Ja, dann erst mal an ein frohes Neues Jahr.
Die Feiertage sind hoffentlich gut überstanden. Und ja,
jetzt haben wir im Prinzip noch den Januar,
wo wir euch da sitze, gerade noch mal so ein bisschen geguckt habe.
Da waren also noch ein vergleichsweise strammes Programm,
wobei ich dann auch sagen muss, in Bezug auf das,
was zum Beispiel jetzt folgt, also dieses Kapitel Assembler-Programmierung,
Hatte ich also auch vorher schon gesagt, also keine Angst,
wenn ich jetzt also hier irgendwie so ein paar Sachen relativ schnell,
relativ zügig vorstelle.
Es geht nicht darum, jetzt wirklich Assembler-Programmierung zu lernen im
Sinne von, ich schreibe irgendwie gut. Das,
was wir früher auch mal als Aufgaben beziehungsweise auch teilweise auf Klausur hatten,
wo es da so ein bisschen darum geht,
also Assembler konnte jetzt nur weitergehen zu verstehen.
Also sprich, da gab es so eine Aufgabe,
wo dann also Assembler-Code vorgegeben war, der sollte dann also quasi kommentiert werden.
Das sollte dann also mal geguckt werden, was könnte dieses Programm vielleicht machen?
Keine Angst, sowas gibt es also inzwischen jetzt auch nicht mehr.
Nichtsdestotrotz für so ein paar Sachen ist Assembler-Programmierung immer noch wichtig und zumindest in Bezug auf ein Grundverständnis,
wie der Software, also sprich der Assembler-Code,
mit der Hardware zusammenspielt und dann eben auch in Bezug auf das Kapitel,
was also jetzt bei mir gerade im Entstehen ist,
wo es also jetzt noch um einige Aspekte von Betriebssystemen geht.
Auch da ist es dann eigentlich wichtig,
so ein bisschen, zumindest ein Hintergrundwissen zu haben,
wie Maschinencode beziehungsweise in diesem Fall an Assembler-Code aussieht,
wie Datenstrukturen im Speicher abgelegt werden,
weil das ist also zum Beispiel gerade eine wichtige Sache in Bezug auf Speicherverwaltung von Betriebssystem.
Ja, ansonsten auch zu den Aufgaben wird es so aussehen.
Also jetzt ist noch ein Aufgabenzettel raus. Es wird auch
noch ein zweiten geben. Dann ist in Bezug auf die Übungsaufgaben
, sagen wir mal, eigentlich Schluss. Das,
was ich dann vielleicht nochmal machen werde, ist,
also sagen wir mal so, für zur Selbstkontrolle,
also jetzt gerade für die letzten Kapitel,
nochmal so ein paar allgemeine Verständnisfragen irgendwie rauszugeben,
zu stellen,
wo sie dann also quasi selber eigentlich nochmal so ein bisschen
gucken können
Ist das jetzt soweit, zumindest in den wichtigen Grundlagen,
angekommen, verstanden worden? Und dafür ist dann zumindest in
den letzten Übungsgruppen, also nochmal so ein bisschen Zeit,
dass dann also da auch nochmal zu besprechen.
Ja, wie gesagt, ich hatte ja vor Weihnachten aufgehört,
so ein bisschen mit dieser Motivation, wofür man Assembler ihm vielleicht noch braucht.
Wie gesagt, das, was ich jetzt hier vorstelle,
ist eben relativ eingeschränkt. Also es geht jetzt also Eine bestimmte Toolchain,
also die wir ja so zum Beispiel so unter Linux irgendwie laufen haben.
Einen einzigen Datentyp, wo dann also wirklich nur mit 64
mit Engineer letztendlich gerechnet wird und von dem X86-Befehlsatz mit mehreren hundert Befehlen werden. Also hier in einigen Beispielen,
also wirklich nur mal so ein paar vorgestellt,
wo es eigentlich nur nur darum geht, zu sehen,
wie funktioniert Assembler-Code prinzipiell, wie werden sowas wie High-Level-Konstrukte,
ein High-Level-Konstrukt des Gehälts,
nein, aber jetzt also irgendwie Konstrukte aus Programmiersprachen,
wenn ich dort jetzt also irgendwie Verzweigung habe unter Programmaufrufe,
wie wird sowas im Assembler, beziehungsweise nachher auf Maschinenebene letztendlich umgesetzt.
Ansonsten gibt es eben da relativ viel an Themen in Bezug auf Assembler,
in Bezug auf Compilerbau, in Bezug auf Systemprogrammierung und,
und, und, die jetzt natürlich nicht weiter angesprochen werden.
Ja, als Programmierer sehe ich eigentlich Assembler bzw. einen Rechner,
erstmal in dem Sinne, wie das,
was ich also zumindest bis zum nächsten Kapitel dann immer noch propagiert habe bzw. propagieren werde,
also Stumpf Abarbeitung nach dem Vor-Neumann-Schema.
Das heißt, ich sage, ich habe irgendwo eine CPU,
in der habe ich eben einen ganzen Satz von Registern, die ich ansprechen kann.
Ich habe irgendwie meinen Programmfehler und habe dann eben einen Speicher,
in dem also sowohl der Programmcode steht, als auch die zugehörigen Daten,
als auch irgendwie so ein paar andere Datenstrukturen, Stack und Teap.
Werden wir diese Woche noch kennenlernen, was sich denn verbirgt,
zumindest beim Stack. Und da läuft dann letztendlich die Befehlsabarbeitung,
also stumpf nach diesem von Neumann-Schema, also das im Programmzähler habe ich jetzt eben irgendwo
Im Hauptspeicher, mein Code, lade dort eben quasi irgendwie
ein Wort. Das wird interpretiert, entsprechend irgendwie abgearbeitet,
je nachdem, was diese Instruktionen letztendlich machen sollen.
Und letztendlich werden dann natürlich irgendwelche Daten von meinem Programm gelesen,
verarbeitet, das tut irgendwas, gibt über irgendein Medium,
dann quasi natürlich Rückmeldung, irgendwas aus, steuert irgendein Prozess,
je nachdem, was das Ganze letztendlich tun soll.
Ja, als Programmierer sieht man also, wie gesagt,
davon einmal den Programmzähler beim 64, Bit X86 ist das RIP,
also die 64-Bit-Register heißen immer irgendwie alle mit R vorne weg.
IP ist dann eben Instruction-Pointer. Dann habe ich also quasi als Programmierer in der Registerbank 16 Register,
die dann eben auch mit irgendwelchen mehr oder minder kryptischen Namen benamst sind,
in einem vorherigen Kapitel, Rechenarchitektur 1,
hatte ich ja mal kurz so ein bisschen was dazu,
an einer Folie mal aufgelegt,
woher dann also letztendlich das Ganze ursprünglich mal gekommen ist,
bis zum 32-Bit-Architekturen hießen die dann eben irgendwie E und dann mit irgendwelchen Bezeichnungen,
jetzt bei 64 Bit-Architekturen heißen die dann mal R.
Und es gibt dann eben jetzt nicht nur 8,
sondern 16 Register und die letzten wurden dann also sinnigerweise nur noch R8 bis R15 durchgenamens.
Gut, dann gibt es eben ein Register,
wo dann also diese Bedingungen Zustandscodes drin sind.
Da wird letztendlich quasi aus der Arrithmetik, aus dem Recheneinheiten festgestellt,
beziehungsweise reingeschrieben, ist irgendwie anreichend Ergebnis, gleich null,
ist ein Überlauf aufgetreten, ist das Ergebnis kleiner null und so weiter.
Und über geeignete Kombinationen davon, dass wir nur heute auch noch sehen,
kriegt man damit dann, also letztendlich alles,
was sich so an Vergleichsoperationen typischerweise habe,
um beispielsweise jetzt eben in mein Programm irgendwie sowas wie Verzweigung abzufragen.
Also in dem Fall bedingte Sprünge wäre da dann
das Stichwort
Gut, der Speicher selber ist eben,
wie ja auch schon angedeutet, oder mehrfach erklärt,
also im Prinzip ein baldweise adressierbares Array, wobei,
wie gesagt, das mit dem Byte Weiser adressieren,
von logisch gesehen, von der Idee her so aufgebaut ist,
rein praktisch lese ich immer ein ganzes Speicherwort und je nachdem,
wie dann also seitens des Compilers bzw. des Assemblers und des Betriebssystems dann quasi der Speicher verwaltet wird,
werden also auch Datenstrukturen,
die dann eben entsprechend kleiner sind als ein ganzes Speicherwort dann,
also auch an diesen Speicherworten ausgerichtet, einfach aus Performancegründen,
damit ich da jetzt also nicht immer umständlich einzelne Bytes jetzt,
also aus 32 oder aus 64-Bit-Worten, dann also irgendwie mal rausmaskieren muss.
Gut, daneben gibt es dann,
also für mich als Programmierer natürlich jetzt also irgendwo die Datenstrukturen,
die ich da jetzt also irgendwo in Form von Variablen angelegt habe,
sah das jetzt also irgendwie, Herr Reis, A-dimensional,
zweidimensional irgendwelche Records, wie auch immer, das dann also letztendlich in den Programmiersprachen aussieht.
Und ich habe dann natürlich immer noch für mich selber jetzt also quasi meinen dynamischen Adressraum,
den ich dann also quasi jetzt über Funktion verwalte,
wie NC, also beispielsweise sowas wie ein Malok,
wo ich dann eben sagen kann,
ich brauche jetzt noch irgendwelchen Speicher für die und die Datenstrukturen,
kann mir den dann also quasi zur Laufzeit aluzipieren lassen und der kommt dann eben von einem Speicherbereich,
dem Hieb, der also sahens des Betriebssystems dann also quasi für meinen Prozess letztendlich verwaltet wird.
Daneben gibt es dann eben auch noch Hauchprozess spezifisch,
dann eben ein Stack als Speicher, also ein Kellerspeicher,
der wird benutzt, um unter Programmaufrufe zu machen,
sprich Parameter zu übergeben, sofern das nicht teilweise in den Registern erfolgt.
Und um dafür zu sorgen, dass dort dann also quasi die gesamte Ablauflogorie entsprechend läuft,
als Programmierer habe ich da dann also auch bedingt Einfluss in der Regel,
möchte ich mich da dann aber eben nicht mit rumschlagen.
Ja, was wir also auch schon mal am Anfang im zweiten Kapitel,
ist es ja schon mal aufgetaucht, gesehen haben,
ist im Prinzip, wie jetzt also aus meinem Programm,
Letztendlich ein ausführbares Programm, also sprich irgendwie eine Art von Binär-Code kommt.
Nicht dort ist das also aufgetaucht im Zusammenhang mit diesen unterschiedlichen Abstraktionsebenen,
den unterschiedlichen Sichtweisen, die man da haben kann,
wo ich dann eben letztendlich dann aber auch das eigentlich so zuordnen kann,
dass ich sagen kann, ich habe also jetzt quasi mein Programm,
kann das jetzt eben mit irgendwelchen Werkzeugen,
also hier ist es mal ein Beispiel von einem C-Compiler,
kompilieren, erzeuge daraus dann also mein Assembler-Programm, kann das dann diesen Assembler-Code,
der dann also auch immer noch Für mich direkt lesbar ist,
dass dann über einen Assembler dann also quasi in Vinya-Dateien umwandeln.
Und was man da natürlich auch noch macht, dadurch,
dass das Programm dann ja entsprechend auch noch irgendwie ein bisschen strukturiert entworfen werden sollte,
also sprich mit einzelnen getrennten Quelldateien,
die ich dann eben auch entsprechend Arzten übersetze,
lasse ich die dann eben hinterher irgendwie dann zu einem insgesamt ausführbaren Programm zusammenlinken kann,
beziehungsweise dann zur Laufzeit irgendwie vom Betriebssystem das Ganze geladen wird und dann eben zur Ausführung gebracht werden kann.
Am Ende dieses Kapitels kommen dann also zu dem Linker und
Loder dann eben auch noch so ein bisschen Sachen
Ja, wie das im Prinzip aussieht,
war jetzt also Teil der einen Übungsaufgabe über Weihnachten.
Ich fühle sie jetzt einfach mal so einfachen Seehut quasi nehmen sollten,
den also einmal letztendlich irgendwie so durch die Toolshane jagen und dann
eben mit so ein paar Tools dann eben mal gucken,
was tut sich da, was kommt da raus.
Auch noch mal so kurz in die Datenstrukturen rein, gucken, wie das aussieht.
Ansonsten, wie gesagt, erster Schritt wäre eben erst,
wenn das Kompliieren von jedenfalls C-Code zu Assembler-Code.
Hier als Beispiel dann eben so eine Funktion,
wo also im Prinzip jetzt zwei Argumente genommen werden, addiert werden durch Aufruf irgendeiner Funktion.
Und das Ergebnis dann eben an eine Speicheradresse dann wieder zurückgeschrieben wird. Und das,
was man da dann, wenn ich den C-Code nehme,
jetzt durch den Assembler schicke, an Assembler-Code rauskommen bekommt,
ist dann im Prinzip sowas, wo man dann eben hier letztendlich,
also irgendwie seine, wie auch immer, aussehenden Assembler-Befehle hat.
Und Hier, diese Prozent, irgendwas, Sachen,
das sind dann eben quasi immer die Register in dem Prozess
und mit denen dann entsprechend jetzt irgendwie was gemacht wird.
Wenn man jetzt genauer reinguckt, der Assembler-Code sieht eigentlich eher so aus wie sowas hier. Wobei alles,
was mit einem Punkt beginnt, also entweder ein Label ist oder bestimmte Anweisungen,
die der Linker dann also letztendlich hinterher noch mit berücksichtigt,
also da geht es dann zum Beispiel darum zu sagen,
wie jetzt Datenstrukturen an Speichergrenzen ausgerichtet werden.
Das sind also hier solche Sachen, unter anderem mit.
Und was dann teilweise vielleicht jetzt also auch noch an Symbolen,
beziehungsweise an Sachen dann, also für das Linken mit anderen Modulen,
dann eben noch global bekannt gemacht werden kann. Der eigentliche Assembler-Code wäre dann eben letztendlich das,
was er sie jetzt also nicht entsprechend eingekraut, dargestellt ist,
dann eben letztendlich, also genau diese Anweisung,
wo man dann eben sehen kann,
gut lässt letztendlich passiert hier irgendwie so ein bisschen so was,
wo also anscheinend irgendwas hin und her bewegt wird,
also zumindest lässt dieses Move darauf vermuten und dann wird mit Call wieder ein Unterprogramm aufgerufen und siehe da,
das sollte uns nicht wundern, nicht genau,
das findet sich hier natürlich auch irgendwo in dem C-Code wieder.
So, was ist dabei dann eben letztendlich immer typisch beim Messembler?
Also theoretisch habe ich da dann eben sehr hardwarenah,
also den Zugriff auf den kompletten Befehlssatz und auf alle Register,
was Im Programmkontext, du laufst seit lang,
ist dann auch wieder nur bedingt stimmt,
also ein paar Sachen sind dann wieder davon abhängig,
wie das Programm jetzt abgearbeitet wird und wie da also jetzt gerade irgendwelche Sicherheitskontexte dann laufen.
Das werde ich auch nochmal ganz kurz in dem Teil zu dem Betriebssystem dann ansprechen,
weil da geht es eben letztendlich darum,
dass man sagen kann, okay,
bestimmte Sachen sind jetzt aus einem Benutzerkontext heraus dann eben nicht direkt zugreifbar,
beziehungsweise in dem Fall wurde es dann eben seitens des Betriebssystems eine Acception geben,
beziehungsweise wenn dann entsprechende Checks eingebaut, dass das dann eben nicht funktionieren kann.
Ansonsten sieht der Sambler-Code eben so aus,
also wie jetzt er sich ja angedeutet,
dass man also quasi jetzt Afriers irgendwo zahlenweise einen Code hat,
dann irgendwelche Memonix,
also sprich sinnvollen Abkürzungen für die einzelnen Maschinenbefehle und sowas
wie Konstanten
Werte, die da also angegeben werden, können dann eben entsprechend entweder als Dezimara-Sexwerte eingegeben werden.
Ja, Sprünge sind dann durch Label möglich. Label wäre zum Beispiel dann also hier sowas wie dieses Zoom Store,
also im Prinzip, also irgendwelche Bezeichner von Unterprogrammen,
nicht hier sieht man ja auch,
also dieses Plus wird dann irgendwo aufgerufen und das war es dann eben durch Linker und Loda dann letztendlich passiert,
ist, dass solche symbolischen Namen nachher im Code,
beziehungsweise teilweise passiert die,
oder die erste Ersetzung passiert eigentlich schon durch den Assembler selber.
Nachher im Code dann eben durch irgendwelche Maschinenadressen ersetzt werden. Nicht,
weil hier kann ich noch sagen, habe ich noch irgendwo einen Aufruf einer Funktion namens Plus.
Rein praktisch passiert dann nachher in der Hardware natürlich,
dass diese Funktion Plus auch irgendwo beginnt mit einer bestimmten Adresse im Hauptspeicher liegt.
Und genau diese Adresse muss dann quasi als Wert,
der dann hier angesprochen wird,
beziehungsweise der dann letztendlich in den Programmzähler geladen wird,
ersetzt werden, sodass dadurch dann letztendlich passiert,
dass eben, wenn die Befehle weiter abgearbeitet werden,
der nächste Befehl dann eben nicht sequenziell in Dealerreihenfolge kommt,
sondern an der Programmzähler dann eben genau die Startadresse von diesem Unterprogramm plus dann eben hat und dann eben von da aus seine Befehle letztendlich holt.
Werden wir hier also auch gleich noch im Beispiel kurz ansehen.
Gut, ansonsten im Assembler gibt es nur primitive Datentypen. Das heißt also,
alles, was ich vorher in meiner Programmiersprache deklariert habe,
also sprich irgendwelche Aggregate, Objekte aus Objektorientierter Programmierung,
gibt es im Assembler nicht.
Im Assembler-Code sind Daten wirklich nur einzelne Bytes oder entsprechend längere Datenstrukturen,
sprich 16, 32, 64-Bit,
die irgendwo im Speicher liegen und die also von dort aus
adressiert werden
Am Ende dieses Kapitels werde ich also noch mal ganz kurz zeigen,
wie sowas, also dann beispielsweise bei Race,
beziehungsweise bei Records dann letztendlich umgesetzt wird. Wie gesagt,
für uns, für die Programmierung müssen wir da jetzt,
also sagen wir jetzt, nichts weiter wissen oder beachten,
weil da kann man sagen,
dafür gibt es eben genau diese Tools,
die das dann letztendlich übersetzen.
Nichtsdestotrotz komme ich gleich darauf. Nichtsdestotrotz sollte man natürlich
schon so ein bisschen wissen,
deswegen also
auch dieses Kapitel
Wie diese Abbildung von Datenstrukturen im Speicher passiert,
weil genau da gibt es eben die Sachen,
wo man sagen kann, also je nachdem, wie ich,
also in meinem Programm mit Datenstrukturen teilweise umgehe,
kann das Ganze effizient oder weniger effizient abgearbeitet werden.
So, jetzt war hier eine Frage. Was machen die Gefühle,
die ein Prozent davor haben? Die Frage war jetzt,
also was machen die Befehle mit diesem Prozent davor?
Also wenn man jetzt hier in diesen Assembler-Code reinsieht,
dann tauchen hier also irgendwie sowas wie diese Prozentdinger
davor auf
Das sind Bezeichner für die Register. Also Befehl ist,
also so rum gesehen, jetzt hier quasi immer eine einzelne Zeile.
Das ist dann der Befehl und der arbeitet eben mit dem Register RBX.
Mach damit irgendwas. Also hier so ein Move ist,
also letztendlich sowas wie eine Art Copy-Befehl.
Also ich habe hier also Quelle und hier Ziel.
In diesem Fall wird eben der Inhalt des Origisters RDX
in das Register RBX einfach kopiert. Aber wie gesagt,
Mit den Registern würde uns also gleich noch mal ein bisschen weiter praktisch begegnen.
Also auch noch die Sache, warum haben einige davon jetzt das auch noch Klammern drum drundherum, folgt dann also gleich noch mal entsprechend in den Sachen. Gut,
also jetzt zu den Datentümen, wie gesagt,
also man hat ja wirklich immer nur die allereinfachsten Datentypen letztendlich für Variablen,
je nachdem, wie die also in beispielsweise jetzt in C,
also diese ganzen Beispiele hier stammen aus dem Buch von Brian und Helleron,
ist dann immer jeweils C-Code,
wie das Ganze dann eben entsprechend umgesetzt wird, nicht,
also 32 oder 64 Bit,
in der Regel dann eben auch oder Unsigned-Darstellung und Textzeichen sind dann,
also Charakter quasi ein 8-Bit, beispielsweise dann in der Darstellung.
Gut, an Befehlen hat man dann eben letztendlich so die üblichen Basisbefehle,
also atmet ja schon logische Funktionen,
die entweder auf Registern oder auf dem Speicher arbeiten können,
nicht dann Datentransfer zwischen Speicher und Registern und letztendlich dann eben Kontrollanweisungen,
also sprich bedingte oder unbedingte Sprünge unter Programmaufrufe und dann,
in dem Fall jetzt also sagen,
dieses Betriebssystem ist dann eher Interraps und Acceptions.
Dazu kommen jetzt also hier bei dem Assembler keine Beispiele,
das werde ich dann also später nochmal bei dem Betriebssystem nochmal ganz kurz was zu sagen.
Bei den Beispielen, wie gesagt, geht es hier also um solche Sachen. Gut,
ansonsten, was man im Assembler natürlich auch immer noch hat,
sind also im Prinzip irgendwelche Makros, also Sachen,
die letztendlich dann also als Folgen von Assembler-Befehlen irgendwo vordefiniert sind,
beziehungsweise in dem Fall,
wenn ich zum Beispiel jetzt bei Programmiersprachen bleibe und mir da
also C als Beispiel nehme,
dann eben aus irgendwie den Standardbibliotheken kommen
Das heißt, wenn ich MC irgendwie in Entklude,
Standard-IO habe, und da ist also irgendeine Funktion Aufrufe,
verbirgt sich dann dahinter letztendlich dann nichts anderes als eben irgendwie der passende Assembler-Code,
der mir das dann als Unterprogrammaufruf dann eben abläuft.
Gut, wenn man das Ganze dann von dem Assembler-Code nimmt,
entsprechend jetzt durch einen Assembler übersetzt,
kommt dann also hier sowas raus,
also irgendwie so eine Folge von irgendwelchen Hexwerten
Jetzt weiß natürlich jeder, was das Ding genau macht.
Nein, das wüsste ich auch nicht. Es interessiert in der Regel
natürlich also auch nur bedingt. Also dass in diesem Fall
wird jetzt also diese Funktion, Funktion irgendwo in 14-Byte irgendwo umgesetzt.
Vom Compiler beziehungsweise dem Assembler wird dann eben irgendwie eine willkürliche Adresse.
Das ist also dieses hier oben jetzt also quasi gewählt,
wo der Co dann eben im Prinzip im Speicher liegt und dann
eben infolge dieser Bytes letztendlich sich jetzt genau dahinter dann diese Assembler-Anweisung verbergen.
Das heißt, das, was der Assembler macht,
ist dann eben quasi den Assembler-Code, eben genau zu so einem Objektcode umsetzen.
Das ist in dem Sinne jetzt erstmal einfach nur eine eins zu eins tabellenartige Umsetzung,
wo man jetzt eben sagt, okay,
ich nehme jetzt also quasi hier mein Assembler-Code,
mache aus diesem Immornix dann eben quasi die passenden Bit-Folgen die dann also hier beispielsweise drinstehen,
also 4, 8, 8, 9, 0,3. Nicht,
was dann also irgendwie ein bestimmter Befehl ist.
Und irgendwelche Sprünge beziehungsweise irgendwelchen relativen Adressierungen können an der Stelle dann eben auch aufgelöst werden,
dadurch, dass sich also hier quasi jetzt irgendwie sowas wie Startadressen habe und weiß,
wenn ich jetzt also hier beispielsweise in dem Programm einen Sprung machen würde,
wieder an diese Stelle oder an dieses Label zurück,
dass ich dann ihm, je nachdem, wo ich hier bin,
dann eben an eine bestimmte Adresse letztendlich springen müsste.
Das ist das, was dann also dort im Prinzip passiert.
Das heißt, daraus wird dann eben aus diesem Assembler-Code sowas wie ein Binärcode.
Da fehlen jetzt natürlich noch die Verknüpfung zwischen den einzelnen Dateien und die Verknüpfung zur Bibliotheksfunktion.
Und das wird dann nachher durch linker und bin da letztendlich aufgelöst,
wo dann eben Laufzeitbibliotheken, also teilweise eben Code aus Bibliotheken.
Entweder statisch hinzugelegt wird, sprich der Code wird kopiert zu
meinem Teil in das Binary mit hineingepackt, macht nun heutzutage eigentlich eher selten.
Oder man macht eben ein dynamisches Linken, das heißt zur Laufzeit,
wenn das Programm ausgeführt wird und ich da dann eben irgendwo den entsprechenden Code habe,
der dann also irgendwie so ein Print F beispielsweise aufrufen würde,
dann würde das Ganze dann eben entsprechend auf das Modul,
wo also quasi das Print F im Hauptspeicher ist,
also irgendwo als Unterprogramm drinsteht,
entspannt passend verweisen.
Das sind dann eben die Sachen, wo Bibliotheken dynamisch zur Laufzeit verknüpft werden.
Gut, ja, wenn man sich jetzt so ein bisschen
nochmal an den Code ansieht, also was passiert da?
Also jetzt beispielsweise, also ich habe hier irgendwo meinen Seko,
da geht es ja also darum,
dass ich also irgendwie den Wert von einer Variablen auf einer Speicheradresse schreibe.
Nicht, dass ich ja quasi jetzt mal diese Pointer-Schreibweise nicht.
Also sprich, in Destination steht dann quasi Adresse,
unter der das Ganze dann gespeichert wird.
Im Assembler-Code sieht das Ganze jetzt quasi so aus,
dass man sich das immer irgendwie so vorstellen kann.
Ich habe hier zwei Register, RAX und RBX.
Und sowas wie so ein Move-Befehl, beziehungsweise in diesem Fall Move Q,
also sprich, der kopiert jetzt also Quad-Worte,
also acht Byte,
würde dann eben den Inhalt von RAX nehmen
Und jetzt nicht in RBX speichern, sondern in die Speicheradresse,
die in RBX steht. Das heißt, diese Klammernschreibweise,
das spricht die Klammern drumherum, sind dann eben ein Verweis in
den Speicher und in RBX steht dann eben die zugehörige Speicheradresse.
Das heißt also, hier habe ich also letztendlich dann eben
genauso was wie dann eben so eine Referenz,
was also quasi diesem Äquivalent in Cedan entsprechen würde.
Also hier ist das Ganze nochmal,
ob man so einer mämonischen Schreibweise dann dargestellt
Gut, im X86-Befehlssatz sieht das Ganze jetzt eben so aus,
dass so ein Move-Kuh-Befehl mit diesen Argumenten kodiert werden würde als 4,
8, 8, 9,03. Also irgendwie so drei Bytes,
die jetzt also irgendwo an einer bestimmten Adresse hier irgendwo in dem Code drinstehen.
Und wenn man jetzt also hier eben nochmal entsprechend hingeht,
sind das also in diesem Fall beispielsweise jetzt diese drei Byte.
So, was man demzufolge dann eben auch machen kann,
ist, wenn ich jetzt also die solchen Binär-Code habe,
also sprich den Objekt der also aus dem Assembler rauskommt,
oder ein ausführbares Programm. Dass man das eben disassemblieren kann,
das ist also auch etwas, was also in einer Übungsaufgabe,
da ist es auch mal mit drin war,
wo man also im Prinzip dieses hier nimmt,
und diese Textersetzung, die der Assembler macht,
jetzt quasi wieder rückwärts machen lässt,
und daraus dann eben so was wie hier so ein Assembler-Code letztendlich raus,
wieder rauskriegt.
Das kann man eben sowohl führen Komplettes ausführbares Programm machen,
was also schon gelingt ist. Oder eben für so eine Teildatei,
wo dann eben jetzt nur irgendwie einzelne Teile oder einzelne Funktionen Prozeduren übersetzt worden sind.
Und wie gesagt, also dieses Object-Damm, das,
was man damit machen kann, funktioniert eben auch erstmal unabhängig vom Betriebssystem.
Weil da geht es ja wirklich nur darum,
dass man sagt, okay, ich habe hier einen Rechner,
da steckt der und der Prozessor mit drin.
Der hat einen bestimmten Befehlssatz. Und dann ist es also erstmal völlig egal,
als ob ich darauf Linux, Windows oder irgendein anderes Betriebssystem installiert habe.
Das, was der Prozess an Befehlen ausführt, ist immer dieser Befehlssatz.
In demzufolge kann ich dann eben auch theoretisch, folgendes machen,
dass ich also jetzt hier irgendwie so ein Windows-Programm nehme,
also in diesem Fall,
dass ich hier mal irgendwie so ein Notpad-Klon, nicht,
dass das einmal durch den Objektdown durchlaufen lasse, nicht.
Und dann kriegt man also hier irgendwie so ein Dissassembly raus,
wo dann also drin steht, was für ein Dateiformat ist
und dann geht das also hier jetzt in diesem Fall,
also mit dem Programmcode irgendwo los.
Nicht das ist also das, was letztendlich binär in der Datei drinsteht.
Wenn ich mir die jetzt also mit einem Binär-Editor irgendwie ansehen würde,
nicht, und das ist dann eben quasi der zugehörige Assembler-Code.
Und das kann man dann eben letztendlich dann also der Reihe
nach für alle Programme irgendwie machen lassen.
Das ist dann eben ein bisschen länglicher, wie man dann teilweise sieht.
Und theoretisch könnte ich damit, was man jetzt natürlich immer nicht darf,
auch versuchen, Äh, this assembly, beziehungsweise Reverse Engineering von irgendwelchen Programmen letztendlich zu machen.
Woran sich die Programme jetzt aber natürlich immer noch unterscheiden,
also das ist dann also auch hier in diesem Dateiformat schon so ein bisschen mit angedeutet,
ist dann die Art und Weise,
wie jetzt also die unterschiedlichen Betriebssysteme die Speicherverwaltung handhaben,
wie die damit umgehen, wie jetzt Unterprogramme behandelt werden,
dass Speicher Layout beziehungsweise die Zuordnung von Programmen zur Hauptspeicher wird dann eben in verschiedenen Betriebssystemen auch unterschiedlich gehandhabt.
Und das macht dann also letztendlich die Unterschiede aus,
weswegen ich jetzt also so ein Notepad Plus Plus,
also hier jetzt, also unter Linux in Zwölfsfallen nicht ausführen kann.
Und spätestens dann, wenn es darum geht,
irgendwie was mit grafischen Oberflächen zu machen,
wird dann natürlich also sowieso alles nicht mehr funktionieren,
weil dann eben die Zugehörigen grafischen Systembibliotheken gar nicht da sind
und das Ganze nicht funktionieren kann
Gut, ja, also bei dem,
was sie jetzt also hier an Assembler-Promierung noch weiter vorstellen werde,
geht es also erstmal so ein bisschen darum,
so ein paar Sachen, Adressierung kurz kennenzulernen,
also sprich, was kann ich jetzt,
also vielleicht außer diesen Prozentregister und diesen in Klammern-Prozent-Register noch irgendwie machen,
wie sehen arrethmetische Operationen aus,
was kann man mit dem Statusregister machen,
also sprich genau,
wie kriegt man jetzt also irgendwie Kontrollstrukturen und Sprünge hin und dann eben so ein bisschen was dem Programmstrukturen.
Wie gesagt, alles an Beispielen kommt hier aus
dem Buch bzw. dem zuhörigen Foliensatz, das heißt,
wenn sich hier mal damit also irgendwie dann nochmal genauer auseinandersetzen möchte.
Wobei, wie gesagt, für die Klausur wird das nicht benötigt, keine Angst.
Kann man sich da dann also entsprechend nochmal irgendwie ein bisschen weiter schlau machen.
Ja, Basisbefehl. Einen haben wir schon kennengelernt,
Movequart, sieht dann also quasi immer so aus,
dass sich Quell und Ziel in dieser Reihenfolge angegeben habe,
transferiert eben ein Longboard und ist also mit eine der häufigsten Instruktionen,
die also letztendlich vorkommt.
Ich hatte ja, als um die Befehlssätze ging,
also ja, dieses Statistik aufgelegt,
nicht was wird am häufigsten gebraucht in Bezug auf Load and Store und so weiter.
Im Prinzip mit diesem Movequat kann ich vom Speicher
in ein Register und vom Register in Speicher transferieren.
Das heißt, das ist quasi sowohl Load als auch
ein Store-Befehl in dem Sinne
Taucht eben vergleichsweise häufig auf. Die Operanten,
die man dort angeben kann, sind typischerweise natürlich irgendwie so was wie Emediate-Wert,
also sprich Konstanten, die werden im X86 Assembler immer dadurch geschrieben,
dass sich also ein Dollar vorneweg schreibe.
Und entweder habe ich dann Dollar und dann eben einfach irgendwie einen ganz normalen Dezimal,
eine ganz normale Dezimalzahl oder wie NC-Schreibweise,
also mit einem 0X als Prefix, also irgendwie so eine Hexzahl.
Nicht Register, Im X86-Sembler hat man dann eben diese 16 Register,
die dann eben alle mit Prozent R und dann eben entsprechenden Kürzeln,
hier sind sie alle mal irgendwie aufgezählt, irgendwie beginnen. Einige von denen,
also deswegen ist dieses RSP ja so ein bisschen eingeführt,
gefärbt, wer dann noch für Spezialaufgaben benutzt, also in diesem Fall,
daher kommen dann nämlich also ursprünglich auch mal diese komischen Akronyme,
SP steht in diesem Fall für Stackpointer,
das kann ich also sagen mal als Benutzer nicht beliebig benutzen,
weil das in der Regel dann nämlich, also das ist,
wo also dann also vom, von der Programmlogik her,
quasi der Stack mitverwaltet wird.
Gut, ja, Speicher, wie gesagt,
immer als Blöcke von Bytes, die dann eben auch beidweise adressiert werden.
So, mit diesem Move-Pee-Q- oder Move-Quot-Befehlen kann ich dann eben
letztendlich also folgendes machen. Ich kann also einen Emediate-Wert entweder
in Register oder in Speicher schreiben
Ich kann den Inhalt eines Registers in den Register oder in den Speicher
schreiben und ich kann aus dem Speicher in ein Register lesen,
Datentransfer von Speicher zu Speicher, ist nicht möglich.
So, hier sind dann immer noch mal so Beispiele,
also sprich Hexadezimal 4 soll nach RAX geschrieben werden oder dezimal
minus 147 soll in den Hauptspeicher geschrieben werden und die Speicheradresse,
wo das landet,
steht in dem Register RAX
Das wäre dann also hier immer quasi die analoge C-Schreibweise. Register,
Registertransfer, entsprechend dieses hier, wenn ich den Speicher als Ziel habe,
wird dann eben quasi immer das Register in Klammern geschrieben,
umgekehrt, so war das ja,
sowas wäre dann eben sowas wie ein Store-Befehl.
Dieses wäre dann quasi das Äquivalent zum Load,
wo ich dann eben Außenspeicher von der Adresse RX,
was lese und in das Register RDX reinschreibe
Ja, Adressierungsarten im Mediat mit dem Dollar vorneweg, Zugriff auf den Register,
dann immer mit Prozent und dann eben der Name des Registers.
Speicherzugriff, dann eben immer letztendlich hier ein Registerzugriff oder
ein Emidiate und dann eben in Klammern,
was dann eben so eine Direfferenzierung für den Speicher macht.
Daneben gibt es dann also noch verschiedene Varianten,
die Adressen oder Speicheradressen dann eben noch weitergehend zu berechnen,
was man also relativ häufig hat, sind solche Displacement-Adressen.
Das heißt, ich habe dann also den Inhalt eines Registers,
das ist die, den nehme ich als Speicheradresse und kann
dazu dann eben noch einen bestimmten Offset hinzuaddieren.
Das heißt, rein logisch, Inhalt von dem Register
wäre dann die Speicheradresse plus auf ZD.
Das ist dann eben dieses Displacement und das Ganze wird dann
beispielsweise dann in so einem Move-Befehl genommen
Wo ich dann eben beispielsweise jetzt also hier den Inhalt von RVP
nehme, damit aus dem Speicher lese, acht hinzuaddiere.
Jetzt kann man sich immer fragen, worum acht. Das,
was ich dort also rein praktisch mache, ist einfach,
dass ich in diesem Fall jetzt also ein komplettes Speicherwort weitergehe.
Nicht, weil Speicheradressierung ist immer weitweise, 64-Bit-Architektur. Das heißt,
mit acht Bite bin ich dann eben quasi mal ein Speicherwort weiter. Nicht so war es,
beziehungsweise da gibt es dann eben auch noch weitere Adressberechnungen,
wird dann eben zum Beispiel immer ganz gerne genommen,
um beispielsweise relativ zu bestimmten Registerinhalten,
die quasi sowas wie Pointer sind,
dann entweder nach oben oder nach unten im Speicher weiter zu verweisen.
Das Ganze wird beispielsweise dann immer ganz gerne bei irgendwelchen Unterprogrammaufrufen benutzt,
um dann eben sowas wie Stack zu verwalten,
hier in dem Code, naja,
also hier in den Beispielen taucht das zumindest bis zu dieser Stelle erstmal nicht weiter auf.
Aber da hat man dann eben letztendlich dann immer sowas wie
diesen Stackpoint da und geht dann immer relativ zum Stackpointer,
entweder nach oben oder nach unten und kann da mit
der Nase entsprechen,
da ist seine Datenstrukturen
zu greifen
Gut, hier dann also noch mal ein Beispiel,
wie jetzt erstmal so ein Move-Wortbefehl benutzt wird.
Wir haben also hier diese wunderbare Funktion namens Swap,
wo ich also quasi hier zwei Argumente bekomme,
beziehungsweise die werden hier also quasi über diese Zeiger referenziert.
Und was gemacht werden soll, soll eben quasi, dass die Inhalte,
die jetzt also unter diesen Adressen XP und YP stehen, vertauscht werden.
Wie macht man das dann, also sinnigerweise? Ich lese also
quasi von der Adresse XP den Inhalt, speichert das in temporäre Variable,
macht das Ganze mit YP und schreibe das dann dementsprechend umgekehrt zurück.
Im Assembler-Code kommt in diesem Fall bei so einer wahnsinnig komplizierten Funktion natürlich irgendwas raus,
was also ähnlich kompliziert aussieht wie der Original-C-Code,
also sprich diese vier Befehle,
wo ich dann eben in dem Fall von dem Inhalt von RDI,
was also dann quasi das eine Argument beinhalten soll,
lese, nach RX schreibe,
von RSI-Lesen nach RDX schreibe und das Ganze umgekehrt wieder zurückschreibe,
ist hier also nochmal so
ein bisschen grafisch visualisiert
Ich angenommen, ich hätte unter diesen Speicheradressen diese Werte 123
und 456 stehen. Nicht, was passiert da?
RDI soll jetzt eben quasi auf diese Adresse zeigen.
RSI soll auf die Adresse zeigen. Und das,
was das Programm dann eben der Reihe nach machen würde,
ist, dass es dann also in diesem Fall jetzt von
der Adresse von RDI aus dem Speicher etwas in der RX liest.
Anschließend von dieser Adresse nach RDX liest, dann eben hier das Ganze zurückschreibt.
Und anschließend habe ich dann eben entsprechend die Inhalte mit so einem Swap vertauscht.
Zu der Adressierung, ich hatte ja eben schon gesagt,
es gibt dann eben noch diese Möglichkeit, mit diesem Offset,
die ganz allgemeine Form im X86 Assembler sieht so aus.
Bislang haben wir also quasi hier diesen bei der Basisadressierung,
also hier quasi ein Register gehabt und dann eben noch ein Displacement.
Ich kann hier noch zwei weitere Argumente haben.
Das heißt, ich habe hier also meinen Basisregister,
habe dann hier noch ein zweites Register.
Das wird dann also auch immer ganz gerne als RI
oder als Indexregister bezeichnet
Da ist die Idee,
dass wenn ich eben beispielsweise durch irgendwelche Rays durchlaufe,
dass ich quasi in einem Basisregister quasi den Zeiger auf das
erste Element oder auf das erste Element oder die Startaufresse des Arays habe und dann mit diesem Indexregister letztendlich genauso,
wie ich das in der Programmiersprache mit meiner Laufvariablen machen würde,
dann eben auf Charakter 0, Charakter 1,
Charakter 2,
entsprechende Reihe nach Durchlaufen würde
Während Charakter jetzt in einem Byte kodiert, würde es jetzt also einfach nur 0-1,
2 und so weiter durchzählen. Wenn die Datenstrukturen größer sind,
dann ist es ganz nett oder ganz sinnvoll,
hier noch diesen Skalierungsfaktor zu benutzen,
weil dann muss ich hier nämlich nicht null vier,
acht, zwölf und so weiter zählen,
sondern könnte dann eben entsprechend Tier 0-1,
2-3 zählen und habe dann hier einen Skalierungsfaktor,
der mir nur für die Datenstrukturen angibt,
wie viele Byte die
jeweils im Speicher belegen
Damit kann man dann eben quasi über so einen Ausdruck im
Assembler-Code letztendlich arathmetisch gesehen, also diese Formeln machen.
Also das heißt, ich habe Basisregister plus skaliert,
das ist in diesem Fall dann einfach nur eine Konstante,
mit diesem Indexregister plus irgendeinem Emedienwert,
den ich dann eben auch noch als zusätzlichen Auszeit mit drauf tun kann.
So, jetzt war eine Frage da. Ja,
das war zu DRS-Bot. Könnte man ja mit drei Operationen
lösen, der Mantel-Eck,
dem Experte
Das ist die... Wo er sagt, wenn man die Umstände nicht speichern kann. Genau,
also hier war jetzt also die Frage,
also im Prinzip könnte ich das jetzt hier natürlich also noch ein bisschen kürzer kodieren,
wenn ich eben jetzt also hier die Möglichkeit hätte,
beispielsweise jetzt von RDI zu lesen.
Das speichere ich hier in einer Variablen inzwischen.
Im zweiten Schritt mache ich dann Datentransfer von Speicher,
auf den Speicher, wo ich dann also quasi direkt hier reinschreibe.
Das war, wenn ich das verstanden habe,
also eigentlich so die Idee
Und gut, das ist jetzt einfach nur das,
also hier im Assembler beziehungsweise eine Art und Weise, wie das Ganze auch einen praktisch benutzt wird.
Eben dieser Datentransfer von Speicher zu Speicher erstmal nicht vorkommt,
sondern das war etwas, was ich kurz vor Weihnachten noch so ein bisschen versucht habe zu motivieren,
dass im Prinzip von den Befehlssätzen her eigentlich sich heutzutage so dieses Riss-Prinzip durchgesetzt hat,
dass man eben sagt, ich habe eben immer nur Datentransfer von Speicher zu Register oder von Register zu Speicher.
Also sprich das, was Lot oder Store-Befehle sind,
ein Datentransfer vom Speicher irgendwo anders in den Hauptspeicher,
was das ja in diesem Fall dann letztendlich implizieren würde,
ist zwar, soweit ich weiß,
eigentlich theoretisch im X86-Befehlsatz noch mit drin,
kein Comparlo oder Assembler, würde das heutzutage allerdings noch generieren,
weil da ist dann auch einfach das Problem,
das ist vom Code oder von der Abarbeitung durch die Hardware unendlich inneneffizient.
Nicht weil, das liegt jetzt wieder an etwas,
was dann also in einem nächsten Kapitel dann nochmal kommen wird,
wo es ein bisschen um diese ganze Speicherhierarchie geht,
hatte ich ja, glaube ich,
auch schon mal ganz kurz irgendwie gesagt oder angedeutet.
Im Prinzip Prozessor ist eben im Vergleich zum Hauptspeicher,
um Faktor 100 mindestens schneller. So,
und jetzt macht es natürlich wenig Sinn zu sagen, ich habe einen Befehl,
wo ich aus dem Speicher was lese und wieder direkt in den Speicher zurückschreibe.
Okay, selbst nur aus dem Speicher lesen und zurückschreiben,
wie das jeder Fall ist. Würde natürlich auch erstmal heißen,
okay, der Prozessor muss 100 Taktzyklen offen Speicher warten.
Das kriegt man jetzt durch einen Cash, das,
was im nächsten Kapitel fork dann wieder so ein bisschen abgestellt.
Aber wenn ich jetzt also auch noch direkt speicher,
hättest du Speichertransfer mache, kann es eben, oder ist die Wahrscheinlichkeit,
dass da Probleme auftreten, also eben noch deutlich größer.
Also das ist immer so etwas, wo man sagen muss,
okay, von der Grundidee des Programmierers,
nach von Neumann-Konzept arbeite ich immer nur mit dem Hauptspeicher.
Heutzutage macht keine Hardware das mehr,
weil dann hätten wir also keine 4 Gigahertz-Rechner,
sondern dann würden die also irgendwie immer noch mit 20 Megahertz
vor sich hin dümpeln,
dann könnte ich direkt einen Speichertransferspeicher zu speichern machen
Dafür brauche ich dann nicht zwingend, was weiß ich,
also irgendwie 500 Euro für eine CPU auszugeben.
Ich scheue das da dann eben einfach von der Performance her
nicht funktioniert. Gut, ja, also wie gesagt,
indizierte Adressierung ist dann eben so eine ganz allgemeine Form,
die sich dann also quasi aus dieser Form theoretisch erstmal berechnet.
Meistens hat man dann eben solche Sachen,
dass sich also entweder nur aus dem Register,
das jetzt als Speicheradresse nehme, Beispiel hatten wir dann auch schon,
wo dann also quasi dann noch irgendwie so ein Offset mit hinzuaddiert wird und was man dann eben häufig nochmal huds sind dann eben diese Sachen,
wo es dann,
also wenn das dann an re-artige Strukturen geht mit diesen Index-Registern arbeitet,
weil man damit dann also auch sehr effizient damit durchlaufen kann.
So, hier ist dann nochmal so ein Beispiel,
wie jetzt solche Ausdrücke in Bezug auf die Adressberechnung quasi aussehen,
beziehungsweise wenn ich jetzt sage,
ich habe in dem Register RDX den Wert F00,
in Register RCX in Wert 0100,
was dann also quasi aus so einem Ausdruck jeweils rauskommen würde,
sprich Die Adresse, die jetzt also rauskommt,
wenn ich RDX nehme, mit Offset 8, wäre dann F-008.
Die Adresse, die ich also erhalte, aus diesem Ausdruck,
sprich RDX plus R-CX, wäre dann F-1-00.
Hier das Ganze dann eben noch mal zu einer Skalierung.
Wäre dann F-4-0-0. Das heißt also,
hier könnte ich jetzt eben zum Beispiel sagen, wenn ich sowas wie ein 32-Bit-Architektur habe,
dann hätte ich also quasi hiermit dann das nächste Speicherwort empfundlichen mit dem zu Graf 4 erhalten.
Gut, und hier dann eben nochmal so ein Beispiel,
wo das dann also jetzt hier nur mit so einem Skalierungsfaktor arbeitet,
nicht ein Gut, letztendlich ist das jetzt also hier nicht die große Raketenwissenschaft,
sondern muss jetzt eben AFM nur sich vergegenwärtigen,
was verbirgt sich hinter diesem Ausdrücken,
nicht und das dann eben entsprechend einsetzen, einmal kurz durchrechnen.
Gut, ja, kommen wir jetzt zu den eigentlichen Funktionen,
die da also irgendwie ausgeführt werden können.
Zum einen hat man also natürlich sowas wie jetzt Ganz normale Operatoren.
Das heißt, Addition, Subtraktion, Multiplikation, ein Linksschieben,
ein Rechtsschieben und beim Rechtsschieben dann eben noch die Unterscheidung zwischen SAR,
so ein arenthmetisches Schieben und SHR für ein logisches Schieben.
Dann Bitvises und Bitvises oder Bitvises XOR und so weiter.
Oder es geht also die Liste, sondern es geht dann noch deutlich weiter.
Was man hier schon sieht, in diesem Fall Befehlssatz,
mit zwei Adressbefehlen. Nicht das heißt,
ich habe immer das Ziel, als den ersten Operanten,
das, was dann eben hier quasi als Source steht,
dann eben zusätzlich dann als den zweiten Operanten und das Ergebnis landet dann eben auch wieder entsprechend hier.
Also da unterscheidet sich dann eben der X86-Befehlssatz,
da sieht man auch, dass das dann so ein bisschen älter ist,
dass jetzt deutlich von dem,
was also beispielsweise bei MIPS oder bei Arm,
also typischen Riskbefehlsätzen üblich ist,
die dann in der Regel als drei Adressbefehlsätze arbeiten
Gut, neben diesen binären Operatoren gibt es dann eben
auch noch einen ganzen Satz von UNEN-Operatoren, inkrementieren,
dekrementieren, Negation und Nord.
Unterschied zwischen den beiden. Weiß das jemand?
Schon länger her. Das sind auch sozusagen die zwei Sachen
Genau. Also die Antwort war, war auch völlig richtig,
also Nord ist im Prinzip so was wie eine Bitvises,
Kippen der Bits, also sprich aus der Null wird eine 1,
aus der 1 wird eine Null, die Negation ist quasi das,
was wir eigentlich jetzt also rechnerisch machen, also sprechen Arithmetisch,
ich interpretiere irgendwie diesen Quadstring als Zahl,
also als in der Scient-Darstellung und mache aus einer positiven,
eine negative Zahl oder umgekehrt.
Rein technisch, das ist auch schon wieder ein bisschen länger her,
was passiert dabei, wie mache ich aus Oder wie bilde ich Zweier-Kompliment-Darstellung?
Das ist nämlich das, was also dem Ganzen dann zugrunde liegt.
In dem Fall nichts anderes als einer Kompliment. Das ist dann letztendlich,
da nehme ich auch nichts anderes als ein Nord, also sprich Bidvises,
Invertieren und eins in der niederrichsten Stelle hin zu addieren.
Also das heißt,
letztendlich sind beides jetzt also hier irgendwie erstmal ähnliche Befehle,
Ergebnis unterscheidet sich so rum gesehen, dadurch,
dass an einer niedrigsten Stelle,
bei dem hier noch eine Eins hinzu addiert wird,
wenn ich also quasi jetzt einen Nord machen würde
Gut, dann gibt es auch noch irgendwie so einen Befehl,
den man, wo der ursprünglich mal im X86-Assembler benutzt wurde,
um jetzt also quasi hier so einen allgemeinen Adressausdruck zu berechnen.
Also dieses hier, der wird ganz gerne aber inzwischen von
den Comparern immer für araddmetische Berechnungen missbraucht.
Das heißt, da kann ich dann nämlich also hier schon
so einen etwas größeren Ausdruck mit einem Assembler-Befehl aus angeben.
Und das in einem Zielregister speichern. Wie gesagt,
ursprünglich war das mal gedacht Adressberechnung. Inzwischen wird das aber
ganz gerne auch für irgendwelche Rathmetik benutzt. So,
hier mal ein Beispiel zu irgendwie Arthmetik oder irgendwie entsprechenden Ausdrücken.
Also ich habe hier also irgendwie einen Code-Schnipsel-C-Programm,
wo ich jetzt also quasi hier drei Argumente XY und Z bekomme.
Das Ganze sollen wir dann eben irgendwie einen Wert als Funktion quasi zurückliefern.
Und das, was also hier jetzt also letztendlich passiert,
also mit diversen temporären Variablen, ist,
dass halt eben quasi X plus Y berechnet wird,
das Ganze dann noch eben einmal plus
Dann habe ich hier nochmal X plus 4 als T3,
dann wird eben hier in diesem Fall Y mal 48 und
dann wird eben dieses Ergebnis plus diesem Ergebnis NT5 gespaltet,
das Ganze mit T2 multipliziert,
sondern sind also hier irgendwie alle Sachen irgendwo mal verwurstet worden und das ist dann eben letztendlich das,
was dann zurückgegeben wird.
So, ob das einen Sinn macht, sei mal dahingestellt oder wofür man das braucht.
Im Assembler-Code wird dieses Ganze hier letztendlich, also durch diese Anweisung implementiert,
sei jetzt sag mal, jedem selber nochmal überlassen,
sich das da so ein bisschen nachzuvollziehen, was da passiert.
Wie gesagt, Quelle ist immer links. Ziel ist rechts
. Hier in einem Kommentar, also nach diesem Nummernzeichen,
sieht man dann immer, was dann also irgendwo damit dargestellt ist. Also sprich,
wenn ich hier sage X plus Y und ich jetzt sage ihm das A,
ein Argument ist, in einem Register r.de,
das anderen im Register RSI,
dann wird hierdurch letztendlich nichts anderes gemacht,
als dass die beiden die Inhalte dieser beiden Register addiert werden und nach Register RAX gespeichert werden.
Ich finde, das ist genau das,
was also diese Zeile in einem C-Code letztendlich macht.
Also was man in diesem Fall jetzt mit diesem L Load Effective Adress macht,
ist eigentlich sowas wie eine Art drei Adressbefehl zu machen,
da eigentlich in einem X-86-Befehlsatz sonst nicht mit drin ist.
Gut, und wie gesagt, so kann man dann eben versuchen,
hier diese ganzen einzelnen Sachen so ein bisschen nachzuvollziehen.
Eine etwas trickige Sache ist,
also hier im Prinzip bei der Berechnung von T4,
wo es also darum geht, Y mal 48.
So, und ich finde, hier also im Code nirgendwo was mit 48.
So, wie wird das Ganze jetzt gemacht oder berechnet?
Ja, und das ist also wieder so ein Beispiel.
Es gab ja also auch mal so ein bisschen Übungsaufgaben,
wo es also darum ging,
also zum Beispiel jetzt irgendwie so bestimmte arathmetische Operationen zu zerlegen
und eben zum Beispiel sowas wie Multiplikationen durch Schieben auszudrücken
Beziehungsweise irgendwelche Berechnungen mit Konstanten, wie in diesem Fall diese 48,
so ein bisschen durch also irgendwie geschickte Zerlegung mit relativ einfachen Operationen zu machen.
Genau das ist in diesem Fall nämlich also passiert.
Und zwar zum einen wird hier der Inhalt von RSI genommen.
Das ist also das Argument Y. Und mit diesem Load-Effective Adress
wird RSI plus Zweimal RSI, das ist ja das,
was also quasi hier rauskommt, nach RDX gespeichert. Das heißt,
ich habe nach Abschluss dieser Anweisung in RDX dreimal RSI beziehungsweise dreimal Y. So,
und was dann passiert, ist,
dass das Ganze dann hier mit der konstanten 4 nach links geschoben wird.
So, vier Band nach links schieben heißt mal zwei,
mal vier mal acht mal
Da ich das Ganze vorher schon quasi mal drei hatte,
habe ich damit mir zusammengebaut, mal drei, mal 16.
Und genau das ist dann nämlich mal 48. Also das nochmal zur Erklärung,
warum sich hier nirgendwo diese Konstante 48 irgendwie findet,
weil das also in diesem Fall der Comparla,
also relativ schlau hiermit gemacht hat, weil eben sowas wie ein Multiplikationsbefehl,
der jetzt zum Beispiel hier verwendet wird,
einfach von der Hardware deutlich aufwendiger ist, eben Prozesse deutlich mehr Zeit kostet,
als zu sagen, ich zerlege das jetzt wie jetzt,
also hier bei solchen Konstanten in Additionen und irgendwelche Schiebeoperationen.
Weil sowas kann ich alles in einem Takt direkt ausführen.
Hier kann es eben entsprechend länger dauern. Ja,
das erst mal so grob zu einem Beispiel,
wie letztendlich so ein bisschen Arthmetik aussieht. Nicht,
das heißt, damit könnte ich jetzt also irgendwo Funktionen berechnen,
also sprich irgendwie arathymetische Ausdrücke, irgendwie Strings manipulieren,
irgendwelchen Datenstocks schon ein bisschen was machen.
Jetzt hat man natürlich typischerweise in seinem Programm auch noch
irgendwie sowas wie Kontrollfluss.
Das heißt,
ich habe irgendwo Verzweigung
Ich habe Schleifen mit drin. Gut, um das zu machen,
gibt es dann eben im X86 Assembler dann eben noch diese ganzen Zustandskurs.
Die kann man, werden durch einige Anweisungen gesetzt.
Ich kann sie eben abfragen. Und damit ist es dann eben letztendlich möglich,
Verzwagung zu implementieren, Schleifen zu machen oder eben sowas wie Switch, also Mehrfachfachverzwagung zu machen.
Basis von dem Ganzen ist dann also dieses ominöse Flex-Register in der Hardware eben letztendlich irgendwo fest vorgesehen,
wo dann also drin steht,
an bestimmten Stellen habe ich Sowas wie ein Sign,
ein Zero, ein Perity, ein Carrie,
was auch immer.
Das Ganze wird letztendlich von der Hardware, sprich von der Alu irgendwo, gespeist.
Da muss ich mich also jetzt nicht drum kümmern, dass da irgendwas drinsteht.
In der Regel frage ich dann eben nur jetzt mit,
Assembler befehlen, sowas wie einzelne Flex oder beziehungsweise Kombination dieser Flex ab.
Und daraus ist es dann eben möglich, solche Verzweigung beziehungsweise bedingten Anweisungen irgendwo auszuführen.
Ja, wie gesagt, aus Programmiersicht hatte ich ja gesagt,
ich habe hier irgendwie jetzt meine 16 Register.
Ich habe den Befehlszähler. Und ich habe dann eben von den Flex,
also das, was mich jetzt als Programmierer erstmal nur interessiert,
also sowas wie Carry, Zero, Sign und Overflow,
die dann eben entsprechend durch Anweisungen aus dem Assembler-Code gesetzt werden.
Und zwar passiert es zum einen durch Jede arathmetische Operation. Das heißt,
wenn ich also sowas wie so ein AdCu mache,
also zwei Zahlen einfach addiere oder zwei Zahlen subtrahiere oder auch
irgendeine Bitweise logische Operation auf diesen Zahlen mache,
dann wird durch die Alu bei dieser Berechnung von diesem Befehl
dann eben oder werden dann eben entsprechend diese Flex hier alle passend gesetzt.
Das heißt, nach irgendwelchen Anweisungen hier kann ich dann eben
quasi diese Flex entsprechend abfragen mit entsprechenden Assembler befehlen und kriege dann eben raus,
ist Einen Übertrag aufgetreten, ist das Ergebnis gleich null.
Ist das Ergebnis kleiner null, wenn ich das als Find interpretiere.
Gut, wobei das natürlich in dem Sinne jetzt trivial ist,
war also eine Zahl. Wir haben immer zwei Komplimentarstellungen,
kleiner null heißt das höchstwertigste Bittes A1. Dann gibt es den Overflow,
wo dann also quasi so eine Bedingung mit drin ist,
waren das zweier Kompliment überläuft.
Dieser Formel beziehungsweise dieser Ausdruck fand sich also dann auch
schon mal Müsste ich jetzt gerade mal gucken.
Ich glaube, Kapitel 5 war das,
wo es dann also im Prinzip um diese Arithmetik ging,
wo dann letztendlich dann also die Bedingungen drin sind.
Also ich habe zwei positive Zahlen. Das ist also hier
dieser Ausdruck und das Ergebnis ist kleiner null.
Dann habe ich also im Prinzip einen positiven Überlauf gehabt oder
wenn ich zwei negative Zahlen habe und das Ergebnis ist größer gleich,
nur dann habe ich ihn in die andere Richtung, den negativen Überlauf gehabt.
Das ist ja immer das Problem bei der Rennmetik mit begrenzter Wortbreite,
was wir nur typischerweise in Computern haben.
Gut, daneben gibt es dann eben noch Befehle um Die Flex explizit zu setzen,
also zum Beispiel so ein Compare-Code, wo im Prinzip nichts anderes passiert,
als letztendlich sowas wie eine Subtraktion,
aber das Ergebnis wird jetzt nicht irgendwo gespeichert in einem Register,
sondern es wird einfach nur quasi jetzt hier die beiden Argumente voneinander subtrahiert.
In diesem Fall dann auch mit Jetzt bin ich hier
gerade selber mal am Schwimmen, weil eigentlich müsste...
Ach nee, doch, ja, stimmt. So ist ASMR 1 müsste das 2,
also genau beim Subkuh wurde dann ja,
also da wäre das das Ziel und dann wäre es immer der Minus der und das Ganze würde hier gespeichert werden.
Nicht in diesem Fall ist das dann also quasi auch dieser Ausdruck,
nur dass das Ergebnis dann eben nicht gespeichert wird.
Ja, dabei wäre dann eben auch entsprechend Deflex gesetzt.
In dem Fall habe ich dann eben Zero,
wenn die Zahn identisch sind, signed,
wenn Source 1 minus Source 2, kleiner Null ist,
beziehungsweise damit kann ich dann eben beispielsweise direkt abfragen,
ist eine Zahl größer als eine andere Zahl.
Nicht, weil das ist letztendlich ja nichts als dieser Ausdruck,
in dem ich den einfach berechne und sage, okay,
wenn das Ergebnis kleiner Null ist,
dann ist Source 2 größer als Source
Und genau das ist dann eben das,
was man letztendlich jetzt in Hochsprachen wieder irgendwo Hardware,
wenn ich dann sage, nicht. Also ist A größer B,
würde dann eben vom Compiler entsprechend in den Assembler-Code so umgesetzt werden,
dass dann genau so war es dann eben dort passieren würde.
Gut, daneben gibt es dann auch noch eine Anweisung,
die dann also dieses Test,
was dann also quasi ein Bit weisen und entspricht.
Auch da letztendlich nichts anderes als dieser Befehl,
nur dass hier das Ergebnis quasi in Source 1 gespeichert wird.
Hier werden einfach nur diese Zero on Science Flag
jeweils gesetzt
Gut, diese Flag-Register kann man jetzt eben mit entsprechenden Befehlen
auslesen und das Ergebnis wird dann in einem Register gespeichert,
wobei eigentlich dort in einem Register auch noch im niederwärtigsten Buyt.
Und dann habe ich dann eben quasi jetzt an Set-Befehlen Equalent,
Nord-Equal Signed oder also in diesem Fall dann also negativ,
nicht negativ größer, größer, leicht, kleiner, kleiner,
gleich, größer als kleiner, als und so weiter,
die sich dann eben einfach logisch gesehen durch entsprechende Kombinationen dieser Flex heraus ergeben.
Wie gesagt, da muss ich mir jetzt also nicht Gedanken machen,
wann werden wie, wo die Flex gesetzt,
sondern ich habe dann eben entweder eine arathmetische Anweisung oder eben hier so eine
entsprechend so eine Compair-Anweisung und anschließend kann ich dann eben mit so einem Setbefehl feststellen,
ist das Ergebnis Gleich null ist es ungleich null,
kleiner Null und so weiter.
Und damit wären dann eben entsprechend diese Flex so ausgewertet.
Hier ist also noch mal so ein Beispiel dafür,
wo jetzt also beispielsweise jetzt zurückgegeben werden soll, ist x größer Y.
In diesem Fall jetzt in C,
war ich da ja so kein Datentubolien habe,
wo dann eben entsprechend null oder irgendwas
ungleich Null rauskommen
Dem Assembler-Code wird also quasi im ersten Schritt so ein Compare gemacht.
Mit den beiden Argumenten dann eben ein Set-Greater,
wo ich dann eben sehe, ist X größer als Y.
Das wird jetzt eben erstmal in unserem niederwärtigen Beiträgister abgespeichert.
Das muss ich dann eben nochmal größer machen.
Und hätte da dann eben nachher in ERX bzw. in RX
dann eben das entsprechende Ergebnis mit drin. Wie gesagt,
warum der Code jetzt genau so aussieht mit diesen Anweisungen,
Kann man jetzt erst mal sagen, nicht den haben Leute gemacht,
die sich damit auskennen, beziehungsweise das ist das,
was der Compiler anderen also irgendwie entsprechend rausschreibt.
Von daher also keine Angst, da müssen wir jetzt also
nicht irgendwie im Prüfungsstress oder in irgendwelchen Übungsaufgaben dann von selber raufkommen.
Gut, ja, damit lassen sich dann natürlich also
irgendwelche Sachen direkt abfragen, in Register abspeichern.
Ich kann das aber auch direkt benutzen, um damit,
also jetzt quasi über diese Flex dann eben auch gleich direkt Sprünge auszuführen.
Nicht, das heißt, da hätte man dann jetzt eben
einen narrethmetischen oder so eine Compare-Anweisung, die setzt die Flags.
Anschließend würde ich dann eben eine von diesen Jump-Anweisungen haben.
Dort werden die Flex dann eben ausgewertet und als weiteres Argument
von dem Jump habe ich dann eben das Sprungziel.
Und in dem Fall, dass das Ganze dann eben wahr wird,
beziehungsweise wahr heißt, NC dann eben immer ungleich null,
also sprich gleich eins, würde der Sprung dann eben ausgeführt werden.
So bei Sprüngen gibt es dann eben natürlich einen unbedingten Sprung,
das heißt, da springe ich immer und ansonsten wird dann
eben auch geeignet von diesen Fleckregistern quasi ein logischer Ausdruck gebildet,
das Ganze ausgewertet. Und ich kann dann eben abfragen,
wenn irgendwie ist gleich null und leicht null habe, negativ,
nicht negativ und so weiter, hätte dann eben entsprechend meine Sache.
So, wo springe ich dann im Koteen? Natürlich an
irgendeine Stelle, die dann auch im Assembler-Code angegegeben sein muss.
In dem Fall dann durch Label. Sieht dann eben so aus,
dass ich nachher zur Laufzeit natürlich, also irgendwo dort Speicheradressen habe.
Im Assembler-Code selber, also mit dem, was jetzt der Compiler,
also schlechter GCC jetzt, also aus meinem C-Code macht,
hätte ich dort dann eben jetzt erst mal so ein Label als symbolischen Namen,
den ich dann eben auch als Sprungziel verwenden kann.
Gut, daneben gibt es aber den Label immer noch die Unterscheidung.
Sind das jetzt also sowas wie globale Bezeichnungen oder eine lokale Bezeichner?
Ich habe Globale bezeichnet, da fangen dann immer mit so
einem Unterstrich an. Gut, ja,
und was dann eben natürlich noch wichtig ist oder gegebenes Erbst,
was diese Label eindeutig sind
So, hier ein Beispiel dafür, wo ich jetzt eben
eine Verzweigung und einen bedingten Sprung habe. Ich habe hier eine Funktion,
die hat zwar Argumente X und Y und soll mir die Differenz im Absolutbetrag jetzt ausgeben,
sprich, was passiert, wenn X größer Y ist,
soll als Ergebnis X-Y gerechnet werden, im anderen Fall,
wenn X-Klarner gleich Y ist,
dann eben Y,
minus X und dieses Ergebnis wird dann dementsprechend zurückgegeben
Im Assembler-Code hätte ich da jetzt eben quasi einen Comperbefehl,
wo jetzt also quasi diese Argumente miteinander verglichen werden.
Wenn ich entsprechend hier jetzt mit einem Planergleich abfrage,
wo ich dann eben letztendlich jetzt als Bedingung habe,
dass RDI, also X, kleinergleich.
RSI ist, dann soll eben zu dem Label L4 gesprungen werden.
Das ist dann eben diese Einsprungsstelle. Wenn nicht, wird,
der Inhalt von RDI dann eben quasi nach RAX geladen.
Davon RSI abgezogen, also in diesem Fall dann eben genau dieser Ausdruck berechnet.
Und diese Prozedur dann eben quasi oder dieses Unterprogramm dann eben beendet,
also sprich zurückgesprungen und das Ergebnis befindet sich dann eben in dem Register RAX.
Wenn ich hier gesprungen bin, wird quasi das ins umgekehrte Reihenfolge gemacht.
Das heißt, RSI wird nach RAX geladen,
RDI davon abgezogen. Auch da habe ich dann RAX das
Ergebnis und ich springe dann eben entsprechend zurück
Gut, das ist also dann die Art und Weise,
wie eben jetzt hier bei sowas aus dem C-Programm dann der passende Assembler-Code gemacht wird.
Auch da muss man jetzt immer sagen,
dieses Beispiel oder diese Beispiele funktionieren jetzt also wieder für ein Betriebssystem,
ein Compiler und eine bestimmte Art der Compiler-Einstellung,
weil also auch da gibt es dann also immer noch vieles an Optimierung,
wo also die Tools inzwischen also einiges machen,
dass das also teilweise vielleicht ein bisschen anders gehandhabt wird
Deswegen hier nur als Anmerkung, dieser Code würde passieren,
also wenn ich das jetzt nehme,
hier jetzt gerade in meinem GCC selber übersetze,
wenn ich da so als Flag No-If-Conversion angebe,
wenn ich das nicht mache, dann wurde da nämlich was anderes bei rauskommen.
Das, was in diesem Fall jetzt erstmal generell nochmal in dem Code passiert,
also wenn ich mir dieses Jahr ansehe, ist ja,
dass ich also hier quasi vergleiche und dann eben an diese Stelle springe.
Und das kann man sich dann immer so übersetzt vorstellen,
dass ich Im Prinzip aus diesem C-Code erst mal sowas wie so einen C-Code mache.
Also sprich, ich habe hier einen Sprung. Das ist
also jetzt hier also durch das allgemein beliebte Go-To beschrieben.
Wo ich jetzt also quasi den Vergleich auf die umgekehrte Bedingung mache,
also logisch umgekehrt. Und wenn das wahr ist,
dann springe ich in den Elspfahl, wenn nicht,
bleibe ich bei dem Senfahl und berechne das Ergebnis und springe dann quasi aus dem Ganzen raus.
Also das wäre jetzt also hier auch noch C-Code.
Der wird dann eben aber entsprechend so umgesetzt,
Letztendlich genauso wie hier, dass dann also sowas dabei rauskommt.
Wenn ich aber funktional entspricht, eigentlich eher dieses dem Assembler-Code,
als das andere. Gut, hier noch mal kurz zusammengefasst,
also das, was man dabei eben letztendlich macht, ist,
dass diese beiden Teilzweige, also das Dan und der Els-Abschnitt,
eben dadurch passieren, dass ich hier mit dem negierten,
mit der negierten Abfrage in den Els-Fall springe.
Ansonsten den Fall hier passend ausführe
So, wenn ich, jetzt habe ich ja gesagt,
Compiler-Abhängigkeit, wenn ich jetzt also diesen Code nehme und hiermit
einen anderen Compiler-Fleck laufen lasse, kommt sowas hierbei raus.
Und dieses hier ist jetzt also Coach,
wo also auf einmal gar keinen Sprungbefehl mehr auftaucht.
Jetzt kann man sich immer fragen, ja,
wie funktioniert das also? Hier wird in RAX
quasi RDI geladen.
Das ist ein Argument
Und davon, er ist ihr abgezogen, das heißt also
an dieser Stelle habe ich in RAX das eine Ergebnis schon komplett berechnet.
Hier wird RSI nach RDX geladen und ich berechne hier quasi
den zweiten Fall. Dann werden RSI und RDI miteinander,
also mit so einem Compare-Befehl, nochmal miteinander verglichen.
Und dann gibt es noch so ein bedingtes Move,
wo im Prinzip Entweder nichts passiert oder RAX mit diesem Ausdruck,
nämlich also für diesen Elsfall quasi überschrieben wird.
Ansonsten ändert sich hier nichts und ich habe in RAX
dann eben letztendlich mein Ergebnis.
Jetzt kann man sich fragen, was soll das?
Weil Funktional gesehen habe ich hier immer,
der eine Ausdruck wird berechnet
Und der andere Ausdruck wird berechnet. Und hier sage ich,
okay, also hier wäre ich fertig. Hier habe ich
ein Ergebnis, das verwerfe ich. Oder in einem anderen Fall habe ich hier irgendwas gemacht, was für die Cards gewesen ist. So,
und da muss er jetzt eben auch wieder sagen,
das sind jetzt also wieder diese Optimierung, diese Tricks da rein,
die jetzt also quasi dadurch reinkommen, dass die Hardware,
die Rechnerarchitektur, jetzt ja also auf einmal sagt, okay,
nach dem, was ich sonst so immer erzählt habe,
nicht von Narman Schema, Befehl abarbeiten und, und,
und, das funktioniert, bei den Rechnern, wenn man sagt, ich habe eine Recheneinheit, eine Alu.
Moderne Prozessoren haben sowas alles nicht mehr. Moderne Prozessoren haben davon abgesehen,
dass sie jetzt also vier oder acht oder noch mehr CPUs,
also Kerne haben, in jedem dieser Kahne vier oder sechs Integrer-Recheneinheiten und mehrere Fließkummer-Recheneinheiten mit drin.
Das heißt, theoretisch kann eine CPU,
der Prozessor in meinem Rechner, also auch mehrere Befehle
gleichzeitig abarbeiten
Das heißt, die gesamte Befehlsabarbeitung funktioniert eigentlich sowieso nicht mehr,
wie ich das jetzt hier immer irgendwie so runterbete.
Der Befehl, dann der Befehl, dann der Befehl.
Sondern das, was rein praktisch passiert, ist,
dass bei der Abarbeitung durch die Maschine geguckt wird,
quasi für den ganzen Satz von Assembler-Befehlen,
welche Recheneinheit ist frei und gibt es zwischen diesen Registern hier irgendwelche Datenabhängigkeiten.
Wenn ich Datenabhängigkeiten habe, dann muss ich natürlich
bestimmte Reihenfolgen einhalten. Wenn ich keine Datenabhängigkeiten habe,
sprich diese beiden Anweisungen und diese beiden Anweisungen,
könnte ich die auch parallel auf zwei in der Gialus
einfach durchrechnen
Nicht. Und dann ist dadurch,
dass ich jetzt quasi in diesem Fall jetzt also einfach zwei Artos benutze,
dieses hier schneller, als wenn ich jetzt sage,
ich mache das also wie in dem,
in dem Fall hier,
wo ich jetzt also entweder hierhin springe oder hierhin springe,
ich war also gerade mit den Sprüngen,
kostet mich das dann also auch nochmal irgendwie zusätzlich Zeit.
Hier werden einfach parallel beide Sachen berechnet und hinterher wird dann
eben entweder das richtige Ergebnis aus RAX direkt genommen oder eben
gegebenenfalls noch durch das andere berechnete Ergebnis überschrieben.
Und ich habe in RAX quasi meinen Returnwert. Also deswegen die Sache,
weswegen es teilweise solche komischen Sachen gibt. Anderer Fall,
wo sowas teilweise auch gemacht wird, wobei man da noch
ein bisschen mehr Aufwand macht oder betreibt, ist bei bestimmten Berechnungen in der Kryptographie.
Und zwar ist es da dann immer ganz wichtig oder ganz interessant,
dass man eigentlich sagt, naja,
Wenn ich mir so angucke, was jetzt so an Hacking-Sachen,
das wird dann also in der Sicherheitsvorlesung von Professor Federath dann sicherlich nochmal alles deutlich genauer kommen,
was man da also alles macht oder theoretisch machen kann,
ist es halt so, dass wenn ich also eben bestimmte arathmetische Operationen mache,
ich ja sagen kann, na gut,
ob ich irgendwas plus eins rechne oder irgendwas plus null,
also zwei Zahlen addiere oder plus null rechne,
nicht warum soll ich plus null rechnen?
Oder wenn ich eine Multiplikation mache,
also das ist dann eigentlich in diesem Fall eine interessante Fall,
und ich rechne mal eins und eine Addition oder mal null und eine Addition,
was sie also bei entsprechend lang Berechnung von Multiplikation letztendlich jaha,
aber dann sagt, naja gut,
also in dem einen Fall muss ich nur schieben,
in dem anderen Fall muss ich schieben und addieren.
Das führt aber dazu, dass, je nachdem,
ob ich quasi mit einer Null oder mit einer Eins rechne,
das Programm unterschiedlich lange Laufzeiten hat.
Das ist die eine Möglichkeit, die andere Möglichkeit ist,
ich kann also irgendwie die Leistungsaufnahme messen,
also ich sprich die Stromaufnahme und stelle fest,
in dem einen Fall verbraucht der Prozess ein bisschen mehr Energie
als in dem anderen Fall
Und das sind so Sachen, wo man sagen kann,
also das Ganze entweder ein Seitenkanal-Attacke,
wo ich quasi indirekt durch Beobachtung des Laufzeitverhaltens oder der Leistungsaufnahme oder elektromagnetischer Abstrahlung oder was man sich da alles irgendwie abstruses ausdenken kann.
Dadurch zum Beispiel Rückschlüsse bekommen, kann auf irgendwelche kryptographischen Schlüsseln. So,
und das möchte natürlich niemand, nicht demzufolge, ist es da so,
dass also gerade bei irgendwelchen Kryptographie-Algorithmen dann also auch zunehmend darauf geachtet wird,
dass die in der Ausführung durch die Prozessoren eben nicht mit irgendwelchen Superoptimierungen laufen,
wo ich dann also irgendwie dann teile mit aus, weglassen kann,
sondern wo man dann eben wirklich ganz gerne mal auch irgendwelche Unsinnsrechnung parallel macht, dadurch dann aber unabhängig von irgendwie solchen Laufzeitphänomenen ist.
Also das nur so ein bisschen als Hintergrund,
weswegen es da dann beispielsweise auch ganz gerne gesehen ist,
also quasi dann sowas wie zwei Operationen,
dann eben sowohl den einen als auch den anderen Fall parallel zu machen.
Gut, das war jetzt aber einmal schon irgendwie auf so
ein bisschen spätere Semester vorgegriffen. Ich habe, wie gesagt,
hier nur zu den Beispielen, was dabei ist hier. Ja,
das, was hier dann eben letztendlich auch noch mit dahinter steht,
ist in dem Fall, ich habe ja gesagt,
also ich habe hier keine Sprünge mit drin
Das hat wieder einen Vorurteil in Bezug auf das,
wie die Befehle durch die Prozessoren abgearbeitet werden.
Das ist also in diesem Fall Stichwort Pipelining, wird uns
dann in dem nächsten Kapitel dann auch noch kurz begegnen.
Ja, ansonsten Nachteil bei der Ausdrücke werden berechnet bei komplizierteren Berechnungen kostet mich das dann natürlich Zeit.
Nicht da möchte, wenn man das dann wieder nicht haben.
Das sind aber im Prinzip dann wieder diese Sachen,
wo man sagen muss, okay,
in der Regel muss ich dann auch immer sagen, wird,
zumindest den meisten von uns, so ein Code hier vielleicht mal jetzt,
also in irgendwie solchen Vorlesungen wie Rechnerstrukturen und Betriebssysteme begegnen.
Ansonsten sowas programmiert man dann nicht selber, nicht,
sondern hat dann eben Compiler, die das machen,
beziehungsweise sorgt dann eben nur mit entsprechenden Compiler-Flex dafür,
dass entweder die ein oder andere Eigenschaft dann eingehalten wird.
Gut, wie gesagt, hier wäre dann eben quasi das Code-Äquivalent,
dass ich sage, ich habe hier mein Sowas wie so ein ternären Operator,
InC, beziehungsweise würde da dann eben quasi einmal den als Ergebnis in diesem Fall ist,
also den einen Ausdruck berechnen, in einem temporären Wert,
dann quasi den anderen Ausdruck berechnen,
dann eben diesen Vergleich machen.
Und beziehungsweise, wenn die Bedingung nicht erfüllt ist,
dann eben quasi das Result dann eben oder das Ergebnis
den Rückgabewert dann eben mit diesem Wert überschreiben.
Ansonsten habe ich hier also das richtige Ergebnis berechnet und
dann gebe ich das Ganze zurück.
Nicht, dass wir dann also da dann eben nochmal entsprechend
das passende Kodequalent
Gut, ja, in dem nächsten Teil geht das jetzt so ein bisschen darum,
wie Schleifen, Schleifen, Bedingungen,
also jetzt nicht nur einfache Verzweigung, sondern eben sowas wie,
ja, Schleifen mit unterschiedlichen Arten und Weisen vorschleifen und,
und, und übersetzt werden.
Da das jetzt also irgendwie aus Zeitgründen also relativ wenig Sinn macht,
also hier ist mit den Folien noch irgendwie auf die letzten anderthalb Minuten anzufangen,
so als das an dieser Stelle gewesen sein.
Am Freitag geht das dann dementsprechend hier weiter und ja,
dann bis Freitag