So. Doch. Ja, also, allen erstmal ein frohes
neues Jahr. Ich hoffe, Sie haben die Feiertage beziehungsweise
die letzte Woche soweit gut und erholsam verbracht.
Ja, jetzt geht es in den Endsport,
bevor ich jetzt also mit der Vorlesung regulär weitermache,
noch kurz einige Ansagen in eigener Sache.
Zum einen werde ich jetzt die nächsten zwei Wochen von
einem Kollegen vertreten,
was aber nichts macht
Weil der Asimsharzer mit der Vorlesung mindestens genauso viel Erfahrung hat
wie ich, wenn nicht noch mehr.
Dann zweite Sache, ich habe das heute jetzt soweit organisiert,
dass wir einen Termin und Vorlesungsräume haben für Probeklausuren.
Demzufolge gibt es dann also hier auf der Webseite von rechner Strukturen,
also zum einmal die Ansage in der letzten Vorlesungswoche,
also sprich Dann in Kalenderwoche 4 wird es dann in den Übungsgruppen wieder zweiten Satz Musterlösungen geben,
der dann entsprechend verteilt wird, ansonsten zu den Aufgaben.
Also es war jetzt im Prinzip heute ein oder ist ja
noch bis Mittag nach Zeit, also heute ein Aufgabenzettel abzugeben,
der für nächste Woche steht dann auch entsprechend schon online und ein Aufgabenzettel 12
wird dann noch folgen und danach kommt dann nichts mehr.
Das heißt also in der letzten Woche der Vorlesung
ist dann eben in den Übungsaufgaben oder in den Übungsgruppen
dann eben nur noch entsprechend Besprechung
Der Ergebnisse und dann eben nochmal verteilen des zweiten Satzes Musterlösung.
Ja, dann geht es für sie natürlich dann, sag ich mal,
nahtlos weiter. Am Montag wird ja dann die SSE-Klausur folgen.
Ich habe dann im Prinzip jetzt für den Donnerstag einen Termin reserviert,
wo es dann also für Rechnerstrukturen dann eben nochmal sowas wie eine Probeklausur geben wird.
Probeklausur wird im Prinzip so ablaufen, wie Dann also in,
sagen wir mal so gut, anderthalb Wochen später,
also am 20. Februar dann ja, also auch die richtige Klausur.
Das heißt, da können sie dann also schon mal sehen,
welche Arten von Aufgabenstellungen kommen, wie das Ganze so ungefähr aussieht.
Wie gesagt, soll von der Idee her so sein,
dass man einfach erst mal sieht, okay,
was kann ich vielleicht, wo muss ich vielleicht noch was machen?
Das heißt, also wir verteilen dann was, sie haben entsprechend Zeit, das zu bearbeiten.
Hinterher wird es dann im Plenum, im Konrad Zusehhör,
soll dann so aussehen, dass wir dann also entsprechend diese Probeklausur dann auch gemeinsam korrigieren,
sodass man also sieht, war es im Prinzip zu machen ist.
Und anschließend ist da nochmal, sagen wir mal so,
Das war es bei uns, aber irgendwie mal so Panikkultorium hieß. Also im Prinzip Tutorium in dem Sinne,
dass, wenn sie jetzt also vielleicht bei der Vorbereitung,
auch wenn das jetzt natürlich also anderthalb Wochen vorher und jetzt stand nur gerade auch eine andere Klausur an,
vielleicht noch nicht so der Fall gewesen ist,
aber dass wenn sie dann also festgestellt haben,
also gut, irgendwas von Rechnerstrukturen möchte ich jetzt,
also nochmal vielleicht mal genauer erklärt haben oder so,
kann man da dann nochmal Fragen stellen.
Ansonsten werde ich zu dem Zeitpunkt oder zu dem Termin,
also auch im Zweifelsfall, also auch nochmal irgendeinen Mechanismus anbieten,
dass sie dann also letztendlich eine Woche später,
wenn sie jetzt wirklich für RRS lernen,
dort dann also auch nochmal irgendwie was fragen können.
Gut, das zudem, dann als zweite Sache jetzt über Weihnachten in dem Aufgabenzettel gab es dann ja also auch eine Aufgabe,
wo es dann also auch darum ging,
also im Prinzip mal selber jetzt einfach C-Code durch einen Compiler zu schicken,
sich mal so ein bisschen so den Assembler-Code anzusehen. Dazu hatte ich,
war es ja dann also hier dann auch ansprechend,
also auf der Unterlagenseite, passend verlinkt war,
unter anderem dann also auch so was wie so eine kleine virtuelle Maschine aufgesetzt,
vor dem Hintergrund, dass jetzt also vielleicht nicht alle jetzt,
also ihren Laptop-PCs, also mit irgendeinem nichts oder Linux-System fahren,
beziehungsweise da dann also im Prinzip, also irgendwie eine 32-Bit-Umgebung,
dass wenn man da jetzt also dies, was jetzt,
also dort, ich glaube, Aufgabe 10.3 oder so war das,
was man dort dann also entsprechend macht,
dass man das dann eben dort, also beispielsweise übersetzen kann.
Dazu vielleicht nun mal Frage ins Plenum,
hat jemand von ihnen das benutzt oder haben sie gesagt,
nö, also ich habe das im Poolraum um Rechenzentrum gemacht oder ich habe also auf meinem Rechnen irgendwie einen C-Compiler drauf, Einfach vielleicht mal Handmeldung.
Gut, virtuelle Maschine wird nicht benötigt. Nein,
also es ist ja jetzt immer Frage,
also auch für uns oder für mich in dem Fall dann also auch,
also lohnt es sich da jetzt,
also irgendwie sowas vorzubereiten oder wird das jetzt gar nicht weiter nachgefragt,
weil sonst hätte ich das auch nochmal irgendwie kurz gezeigt oder so.
Gut. Ja, dann, wie gesagt, das jetzt eigentlich dazu,
dann kommen wir ganz normal mit dem Stoff jetzt also entsprechend weiter. Kapitel 15,
Assembler-Programmierung, da hatte ich ja letztes Mal im Prinzip also auch schon angedeutet,
dass es also hier jetzt nicht primär darum geht, also irgendwie x86 Assembler-Code zu lernen,
sondern einfach nur so ein bisschen dieses Grundverständnis zu entwickeln,
wie jetzt eben letztendlich sowas wie Maschinencode auf der Hardware abgearbeitet werden kann und das dann eben jetzt hier in so einem relativ einfachen Oder einfach abgespeckten Beispiel.
Ja, hier nochmal eine Folie,
die ja also auch schon auflag, im vorvorvorletzten Kapitel,
wo es ja im Prinzip in der Summe das Kompilierungssystem ging,
also das, wo wir uns jetzt hier bewegen,
ist ja, dass man im Prinzip sagt,
also ich habe jetzt hier meinen Code in diesem Fall,
Programmiersprache, C, was wird ja das Beispiel genommen haben,
nicht was sich dann eben entsprechend durch einen Bre-Prozessor bzw. durch einen Compiler dann eben in Assembler-Code übersetzen kann und Dass dann mit einem Assembler eben ein entsprechendes Binärformat übersetzen kann,
mit dem Linker,
dann mit externen Bibliotheken dann eben wirklich zu einem ausführbaren Programm zusammenbinden kann.
Und wie gesagt, das, was man hier sieht,
ist dann eben eigentlich das,
wo man dann eben nochmal wieder ganz zurückkommt,
so ganz auf den Anfang,
im Prinzip verschiedene Repräsentationen des Programms eben von der ursprünglichen Hochsprachen-Co.
dann eben wirklich bis zu irgendwelchen Binärformaten aus 01
und was dann eben wirklich Maschinen Gut ist.
Und der wird ja dann eben entsprechend auf der Hardware abgearbeitet. Das ist also das,
wo ich ja dann letztendlich im letzten Kapitel abgeschlossen hatte,
dass man sagt, okay, also vorher welche Grundstrukturen an Hardware gibt es.
Und wenn ich jetzt eben so ein beispielsweise 32 Bitstring aus Nullen und Einsen habe und den jetzt eben als einen Befehl interpretiere,
dass man den dann eben entsprechend in Befehlsformate unterteilt und sagen kann,
okay, mit den fünf Bit dekodiere ich dann eben,
das ist der und der Befehl und mit denen Bits dekodiere ich dann eben,
mit welchen Argumenten gerechnet wird und so weiter.
Und das Ganze dient dann ja eben dazu, das,
was eigentlich drunter liegt,
also sprich bei der zugrunde liegenden Hardware dann eben irgendwelche Steuerleitungen in den Alus in irgendwelchen Multiplexer-Strukturen,
was auch immer, je nachdem wie die Hardware da ausgelegt ist,
dann eben entsprechend anzusteuern und dann eben das Ganze wirklich physikalisch ausführen zu lassen.
Gut, hier also auch nochmal diese Repräsentation, dieses Kompilierungssystems,
also jetzt hier von Patterson Tennessee, nicht also C-Code,
wenn ich dann, also hier ist also irgendwie sowas wie Assembler-Code,
wo man dann eben letztendlich in C-Code dann eben schon auf irgendwelche elementaren Maschinenoperationen gebrochen hat, nicht?
Und das war es dann eben aus dem eigentlichen Assembler dann eben rausfällt,
ist dann eben irgendwie dann letztendlich nur eine Folge.
Also im Prinzip von binär interpretierten werden. Gut, einleitend,
hatte ich ja schon mal gesagt, warum jetzt überhaupt Assembler?
Also da muss man auch sagen, gut,
also inzwischen sind wir nun glücklicherweise so, dass Programme eigentlich nur noch vergleichsweise selten direkt Assis in der Sambler-Code geschrieben werden. Da arbeitet man natürlich mit Hochsprachen,
also allein schon deswegen, weil wenn man sagen kann,
wenn ich jetzt also auch nur eine einigermaßen vernünftige Funktionalität reinbringen möchte,
dann möchte ich das also jetzt nicht mit irgendwelchen Assembler befehlen auf unterster Ebene machen,
sondern dafür hat man jetzt ja entsprechend seine Konstrukte,
wo man mit umfangreichen Programmbibliotheken arbeitet, wo man mit objektorientierten Konzepten arbeitet,
wo man dann eben wirklich relativ schnell relativ produktiv,
also das, was ihn dann ja letztendlich in Softwareentwicklung begegnet ist,
eben dann schon wirklich zu irgendwelchen Programmen kommt,
aber jetzt nicht mehr auf unterster Ebene anfängt,
also einzelne Bits, ich sage jetzt mal,
hin und her zu schubsen.
Nicht demzufolge, wie gesagt,
Programmentwicklung und Hochsprachen und in der Regel hat man eben sehr gute Compiler beziehungsweise Tools,
die im Zweifelsphase deutlich effizienteren Code erzeugen als all das,
was ich jemals irgendwie programmieren könnte, wenn ich jetzt versuchen wollte,
hier irgendwie sowas wie Assembler Code selber zu schreiben.
Das geht dann also sogar noch so weit,
wird ihn dann in der übernächsten Übungsaufgabe begegnen, dass,
also je nach Compiler Option dann, also teilweise wirklich Sehr,
sehr starke, sehr gute Optimierung stattfinden, dass man also ein Programm,
was jetzt zum Beispiel relativ ungünstig rekursiv definiert ist vom Compiler in eine iterative Formung gesetzt wird.
Also das fand ich schon relativ stark, dass das so gut geht. Gut,
nichtsdestotrotz, weswegen jetzt also dieses Kapitel,
also ein gewisses Grundwissen, wie gesagt, also allein schon,
um jetzt diese Schnittstelle zwischen Programmierung und Hardware zu beleuchten,
Braucht man natürlich dann die Sache, wenn es jetzt also darum geht,
dass ich also im Prinzip in meinem Programm dann also wirklich auf unteren Ebenen irgendwelche Fehler habe,
die baggen, ist es also auch nicht verkehrt,
zu wissen, wie letztendlich die Abarbeitung von Maschinenco dann eben aussieht,
weil wenn ich jetzt eben mein Programm habe und dann wirklich mit dem Deeburger dort reingehe,
kann es eben sein, dass ich dann eben beispielsweise jetzt nicht den Quellcode zur Verfügung habe, sondern dann mit dem Deeburger, also auf einmal wirklich, also mit Dessemblercode arbeiten kann, beziehungsweise Moos.
Gut, dann, wie gesagt,
Das, was sie als Programmleistung verstärken, bezeichnet ist.
Nicht, dass man eben sagt, viele Sachen,
die ja jetzt vielleicht unwissentlich ineffizient programmiert worden sind.
Also da ist es eben auch ganz gut zu wissen,
was ich eigentlich darunter auf der Maschine letztendlich abspielt,
dass man da eben versucht, oder da dann eben auch entsprechend effizient arbeiten kann.
Gut, und wo man das natürlich originär braucht,
ist es letztendlich, wenn man Compilerbau betreibt.
Das heißt also jetzt selber also irgendwie ein Compiler programmieren möchte
Wenn man, auf unterster Ebene, also im Prinzip sowas
wie Betriebssysteme implementieren möchte oder eben jetzt für irgendwelche eigene Hardware,
die da also irgendwie jetzt zusätzlich angeschlossen wird, sowas wie Gerätetreiber schreiben möchte.
Gut, in der Regel wird man da natürlich dann also
auch erstmal irgendwie ein C- oder Hochsprachen arbeiten, aber bei einigen Sachen ist es dann eben auch schon nötig oder sinnvoll, also vielleicht ein bisschen Assembler-Kenntnisse zu besetzen. Gut,
das, was hier eben, wie gesagt,
vermittelt werden soll, ist im Prinzip so ein Grundverständnis,
wie eben die Programme abgearbeitet werden, wie das,
was sich jetzt, dass ich auf den Folien habe,
wie im Prinzip solche Operationen beschrieben werden,
wie man Kontrollstrukturen implementiert, einen ganz kurzen Hinweis darauf,
wie im Prinzip dann sowas wie Datenstrukturen,
wie man sie jetzt, also in Programmiersprachen hat,
also irgendwie Arrays, Records, Zeiger, du warst alles,
wie das aussieht, beziehungsweise ganz am Ende,
das ist dann,
was sie unten beschrieben ist,
wie man also beispielsweise sowas wie Objektorientierung dann eben auch wirklich
auf diese Ebene mitbringen kann
Gut, dann werden wir uns auch mit Funktionsaufrufen beschäftigen.
Also sprich, wie man Funktionen, Funktionsaufrufe letztendlich implementiert,
wobei da muss man auch immer sagen,
das ist dann natürlich letztendlich dann wieder vom zugrunde liegenden Betriebssystem abhängig,
wie da jetzt Konventionen hinsichtlich des Speicheraufbaus und der gesamten Unterprogrammverwaltung aussehen.
Also wie gesagt, hier ist es jetzt am Beispiel eines Standard-Linux-Systems,
Also bei Windows wurde das dann wieder ein bisschen anders aussehen.
Aber sagen wir mal, so diese Grundprinzipien sind eigentlich bei
allen Betriebssystemen mehr oder minder ähnlich. Ja, Speicherverwaltung,
Funktionsbibliotheken werden ja auch noch ganz kurz darauf eingehen.
Ja, nochmal zur Speicherverwaltung. Wie gesagt, Speicher.
In dem Sinne kann man eben oder wird das,
was ja in der,
in dem letzten Kapitel auch schon angesprochen wurde,
wird dann eigentlich
mal logisch unterzahlt in mehrere Bereiche
Nämlich einmal das, wo ich jetzt eigentlich meinen eigentlichen Programmcode habe, nicht dann sowas, wo Funktionsbibliotheken liegen.
Nicht dann für die einzelne Funktion sowas wie Stack-Bereiche, was also,
wie wir dann also sehen werden, also beispielsweise genutzt wird,
um eben jetzt Unterprogramme abzuarbeiten, nicht dann eben irgendwelche Datenbereiche,
die jetzt also quasi dynamisch verwaltet werden oder eben entsprechend statisch mit drin sind.
Und dann eben noch bestimmte Sachen, die also beispielsweise jetzt von der Betrieb Mittelverwaltung, also sprich vom Betriebssystem dann irgendwie verwaltet werden, wie Interrats, System-Calls oder irgendwelche Exceptions. Gut,
weswegen, Accemder oder eben so ein Code dann eben teilweise auch noch interessant ist,
wobei ich da jetzt also auch nicht weiter in die Tiefe reingehen werde,
beziehungsweise da kann ich dann also auch nur irgendwo später in das Studium,
also in die entsprechenden Arbeitsbereiche zu den Themen Sicherheit verweisen,
wo man dann eben sagt, also letztendlich alles,
was Sicherheitslücken sind, die irgendwo ausgenutzt werden,
basiert zum großen Teil dann eben wirklich also auf irgendwelchen Mechanismen,
die bei der Abarbeitung von Benutzerprogrammen,
bei der Speicherverwaltung letztendlich irgendwo stattfinden, wo man eben sagen kann,
letztendlich gibt es Versagens des Betriebssystems,
inzwischen also schon relativ viele und relativ umfangreiche Mechanismen,
mit denen man eben versucht,
also letztendlich sowas wie ein Speicherschutz einzurichten,
aber es gibt in dem Sinne jetzt eben keine richtige Hardwareseitige
Trennung oder irgendeinen Mechanismus,
der dort also quasi eine Sicherheit gewährleistet
Sodass es eben doch immer mal vorkommt, dass also selbst,
wenn man jetzt sagt, also okay,
nach 30 Jahren Entwicklung sollten eigentlich also irgendwelche Betriebssystembibliotheken oder irgendwelche Verschlüsselungsbibliotheken soweit sicher sein oder sicher implementiert sein,
dass man dann eben feststellt, in irgendwelchen Systembibliotheken ist dann doch vielleicht noch was,
unsauber kodiert und das kann dann eben ausgenutzt werden,
um quasi, wenn ich diese Programme oder diese Funktionen benutze,
dass ich dann eben quasi aus meinem Benutzerprogramm,
also auf einmal mit erweiterten Rechten in irgendwelche Bereiche reinkomme,
die dann eben eigentlich privilegierteren Prozessen, also in diesem Fall dann also in dem Betriebssystem gehören und man damit dann dementsprechend Unsinn machen kann.
Gut, dazu wird also hier in dem Code also nur ganz kurz im Prinzip so ein bisschen was auf Speicherverwaltung und sowas wie Pufferüberläufe eingegangen,
also wo es dann also auch gerade so um diesen Mechanismus der Unterprogrammaufrufe reingeht,
genauer beziehungsweise eben auch gerade mit Beispielen oder sowas,
da fehlt uns leider die Zeit,
also in dem Buch von Brian und Ohalaron,
was ja, also hier dann also auch gerade jetzt,
also bei diesem Teil dann immer die Referenz ist oder das Beispiel,
was dann genommen wird, gibt es dann also beispielsweise,
wenn sie dann mal reingucken, dann also auch sowas wie irgendwelche Übungsaufgaben,
wo man dann selber mal sowas,
also wirklich an so einem kleinen Beispiel Assembler dann eben auch kodieren soll,
aber wie gesagt,
also daraus jetzt hier bei uns eine Übung draus zu machen,
da müsste man also deutlich mehr Zeit haben und deutlich tiefer,
also hier in die Assembler-Programmierung einsteigen,
also deswegen bleibt ihnen das,
also auch wenn das sicherlich ein interessantes Thema ist,
Gut, wie ich schon gesagt hatte, also Sambler hier in Rechnerstrukturen,
also wir beschränken uns hier also wirklich nur auf so ganz einfache Konzepte.
Als Beispiel habe ich jetzt hier immer das,
was also jetzt also auch aus dem Buch von Brian und der Gorellain genommen ist,
den Gnur-Assembler, X86, 32 Bittbefehlssatz,
also auch das ist inzwischen nicht mehr so ganz aktuell.
Hinsichtlich der Datentypen werden uns dort eigentlich auch nur sowas wie Entager
begegnen und von dem gesamten X86-Befehlssatz eben auch wirklich nur ein sehr,
sehr kleiner Ausschnitt des gesamten Befehlssatzes.
Demzufolge kann man sich ja so eine beliebig lange Liste ausdenken,
was jetzt also hier eigentlich noch fehlt.
Also Makros, wie ist der Assembler rein praktisch implementiert?
Welche Konsequenzen ergeben die Chance? Also für ein effizientes Programmieren
nicht? Also auch das macht eigentlich nur Sinn,
wenn man da jetzt also unten auf Assembler-Ebene ist.
Inzwischen würde ich da auch immer sagen, gut,
also bei aktuellen Compilern ist man,
sollte man sich da also erstmal nicht zu viele Gedanken drum machen.
Und dann eben solche Sachen, okay,
wie sieht das dann wirklich mit der Systemprogrammierung aus
Und gut, alles, was da noch drüber hinausgeht,
Gleitkomma-Berechnung und so weiter, fehlt hier auch. Gut,
was sehen wir jetzt als Programmierer eigentlich erst mal nur von einem Assembler,
also das, was wir jetzt sagen, ist,
okay, wir haben eine CPU,
da soll jetzt mein Code also irgendwo ausgeführt werden.
In dieser CPU habe ich den Befehlszähler. Heißt dann hier auf den Folien immer EIP,
das ist also dann quasi der Instruction-Pointer, also sprich,
dass Register wo ich dann also quasi in meinem Programmcode den nächsten Befehl lesen kann.
Nicht, dann habe ich eben einen Satz von Registern.
Hier jetzt eben auch eingeschränkt auf die X86-Architektur mit 32 BitWord-Breite.
Dann ist dieser Satz von Registern, also auch mit acht extrem klein.
Und es gibt dann eben noch sowas wie dieses ausgewählte Flag-Register,
wo dann im Prinzip also irgendwelche Bedingungen oder irgendwelche Flexs explizit gesetzt werden von arenthmetischen bzw. von Testbefehlen,
die ich dann eben benutzen kann, um Overflow,
Zero und sowas entsprechend abzufragen, um daraus dann eben beispielsweise bedingte Sprünge zu machen.
Gut, und dann gibt es im Prinzip ein Memory,
Hauptspeicher, ist für mich als Programmierer erstmal beliebig groß,
möchte entsprechend der Datentypen, kann ich jetzt eben sagen,
okay, wenn ich hier mit 32 mit Entager rechne,
habe ich dann eben zwar hoch 32 Byte,
die ich einfach irgendwie adressieren kann,
also auch wenn das also die wenigsten reicht nahe.
Nein, also inzwischen Tierrechner, ja, aber da nicht,
also dass man dann bei 4 Gigabyte,
aber wenn ich jetzt also entsprechend nicht 32,
sondern 64 Bit nehme, also dann hat man eben entsprechend irgendwelche Speichergrößen,
die also normale Standardrechner, also bei weitem nicht besitzen.
Aber im Prinzip erstmal unendlich, nein, unendlich nicht,
aber eben einen sehr, sehr großen Speicher und irgendwo in
diesen Speicher
Ist dann im Prinzip sowas wie ein Stack. Das heißt,
der Bereich, der dann also beispielsweise genutzt wird,
um jetzt bei dem X-686 Assembler, beziehungsweise hier bei dem Linux-Betriebssystem,
das dann darüber dann eben unter Programmaufrufe zu machen.
Das heißt, darüber werden dann Parameter übergeben,
beziehungsweise Rückgabewerte gegebenenfalls, dann also auch noch gehandhabt,
was man dann eben damit macht.
Wie gesagt, das wird uns dann also
gleich noch ein bisschen weitergegeben
Gut aus, Assembler-Sicht in dem Code, was findet man?
Also wie gesagt, also Arme, diesen Instruction-Pointer,
Programmzähler, dann eben die Registerbank, also diese acht Register,
werden später auch noch kommen, nahmen, müssen sie nicht zwingend wissen.
Wie gesagt, also haben jetzt hier also irgendwie bestimmte Bezeichner,
aber gut, das ist jetzt also nichts weiter. Gut,
dort hat man typischerweise natürlich irgendwelche Programmdaten, die häufig genutzt werden,
also sprich irgendwie sowas wie Zwischenergebnis der temporäre Variablen irgendwas,
womit ich also erst mal relativ viel drauf rumrechne.
Ansonsten hat man jetzt natürlich bei acht Registern,
also jetzt nicht die große Auswahl,
um da jetzt also quasi in seinen Algorithmen letztendlich irgendwelche Datenstrukturen reinzulegen,
damit zu arbeiten,
sondern also man ist da dann eben letztendlich immer darauf angewiesen,
relativ viel, also aus dem Hauptspeicher zu laden bzw. wieder in den Hauptspeicher,
auf entsprechende Variablen dort eben zurückzuschreiben.
Und dann gibt es eben dieses Flag-Register, wo dann eben letztendlich sowas wie Statusinformation dran sind. Speicher,
das Thema hatten wir im Prinzip dann auch in den letzten beiden Kapiteln,
hier jetzt betrachtet ehemals ein, byteweise adressierbares Array,
wo ich dann eben sowohl meinen Programmcode als auch eben meine Benutzerdaten habe und in denen eingeblendet dann eben letztendlich dann, also dieser Stack mit drin ist.
Gut, und das, was man dann ja eben macht,
was jetzt beispielsweise auch eine Übungsaufgabe war, nicht,
dass man sagt, okay, ich habe jetzt also hier mein C-Programm,
rufe den ihr da jetzt eben irgendwie ein Compiler,
also beispielsweise ein GCC auf, erzeugt mir daraus,
dann ist es quasi mein Assembler-Code,
nicht und kann das Ganze dann eben mit dem Assembler in eine Objektdatei übersetzen, also entsprechend wie Nährprogramm.
Und wenn ich daraus dann eben wirklich mein ausführbares Programm mache,
habe ich eben entweder noch Bibliotheksfunktionen,
die quasi statisch gelingt werden, das heißt,
ich benutze jetzt eben irgendeine Bibliothek und die in meinem Programm benutzten und da Programme,
dort wird dann eben quasi der entsprechende Assembler-Quote beziehungsweise diese entsprechenden Objektdatei oder das,
was dann dort eben dazugehört, in mein Programm mit eingebaut.
Oder das, was man heute eigentlich typischerweise meistens macht,
ist, dass man dann eben dynamisch schlingt. Das heißt,
Bibliotheksfunktionen, die eben ständig und häufig benutzt werden zur String-Formatierung zu
irgendwelchen grafischen einen Ausgaben zur Behandlung von Tastatur- oder Maus-Events oder was auch immer man als,
also sich denkt,
werden jetzt von vergleichsweise vielen Programmen und vom Betriebssystem und was auch immer eben gleichzeitig noch mitbenutzt,
da macht es natürlich keinen Sinn,
die quasi statisch zu lenken.
Das heißt also dort irgendwelche Aufrufe jedes Mal in das eigentliche Programm mit reinzubringen,
sondern zur Laufzeit wird dann eben nur entsprechend der Code genommen
und dann eben quasi in dem Kontext des Programms dann eben ausgeführt als Unterprogramm.
Gut, wie das genau aussieht,
wie man das unterscheidet, wird uns dann also später auch
noch kurz begehen
Gut, ja, das,
was letztendlich dann also der Assembler macht, also sprich,
wenn ich jetzt also meinen C-Code nehme und daraus SM-Blacode generiere,
hier also für die sehr anspruchsvolle Funktion namens Sam,
die quasi zwei Entager-Argumente hat,
ein Ergebnis berechnet und dieses Ergebnis zurückliefert.
Würde jetzt also von sehr einfachen X86 Assembler
dann eben so aussehen, dass ich quasi diesen Code hier
irgendwie rausbekomme
Wo man dann eben sieht, ich habe jetzt in irgendwelche Instruktionen.
Ich habe dann eben hier jetzt meine Argumente,
die dann eben entweder Register sind oder sich eben auf irgendwelche Speicheradressen beziehen für Ergebnisse.
Und dann ist das Ganze eben letztendlich,
wie man hier dann auch sieht, mit so einer Art Label versehen,
nicht weil, wie man das hier auch hat,
dass es dann also quasi eine Unterfunktion, die dann also von meinem Hauptprogramm,
also dem Main dann quasi aufgerufen werden würde, und das ist,
was hier letztendlich an diesem Code Den meisten Teil ausmacht,
ist eigentlich erstmal diese Behandlung, dieses Unterprogrammaufrufs.
Wie gesagt, was sich genau dahinter verbirgt,
das soll dann eigentlich im Laufe dieser Woche noch ein bisschen klarer werden,
was damit also dann gemeint ist. Gut, ja,
das, was ihm letztendlich dann mit dem Assembler so beschrieben wird,
ist also eben die Hardware nahe Programmierung,
wo ich dann eben wirklich sage,
ich habe Zugriff,
also auf den Befehlssatz und eben diese Maschinenregister
Der Kurt selber ist dann, wenn man ja hier nur sieht,
also irgendwie dann quasi zahlenweise beschrieben. Ein Befehl pro Zeile.
Ich habe dann eben in irgendeiner mämonischen Darstellung meiner einzelnen Maschinenbefehle,
also hier in einem Beispiel habe ich dann eben sowas wie so ein Push-Long,
Mooflogg, Mooflogg, AdLong, nicht wie auch immer,
die dann also aus dem Intel Programmiermanual heraus,
also irgendwie heißen mögen
Und habe dann eben teilweise konstant mit drin und habe dann eben
entsprechende Referenzen eben entsprechend entweder auf meinem Register oder eben auf irgendwelche Speicheradressen. Hier auch noch zur Konvention,
also im Prinzip bei dem Gnu-Assembler, also das,
was wir hier benutzen, sieht das immer so aus,
dass wenn ich jetzt also mehrere Argumente habe,
das Ziel, eine Operation dann eben immer rechts steht,
wird aber gleich noch an entsprechend vielen Beispielen,
also auch nochmal gezeigt,
was ich dann eigentlich genau dahinter verbirgt
Gut, symbolisches Label, für Sprungadressen. Nicht,
das ist also das, was dann, also hier letztendlich hiermit gemeint ist.
Also das ist dann eben einfach erstmal irgendwie sowas wie ein Label.
Das ist dann eben auch das,
wo im Prinzip dann noch der Unterschied ist zwischen Assembler-Code,
dem Binär-Code und dem, was dann nachher,
also letztendlich durch den Linker erzeugt wird und dann wirklich zur Ausführung kommt.
Nämlich, dass man sagt,
hier an dieser Stelle habe ich noch ein Label,
wenn ich das irgendwo im Binär-Code habe,
dann habe ich dort also schon sowas wie so eine jetzt mal temporäre Adresse,
nämlich also eine Adresse relativ zu einer Startadresse von meinem Code.
Und wenn ich das Ganze dann eben jetzt wirklich auf dem Rechner ausführe,
das heißt also den Linker und den Lademechanismus dann anwerfe,
das Ganze dann also entsprechend zur Ausführung bringt.
Nicht dann wird diese Adresse natürlich auch nochmal umgesetzt,
weil letztendlich in, ich sag jetzt mal,
jedem Programm, was ich jetzt bearbeite, kompiliere,
nicht der Programmcode quasi anfängt von Adresse 0 bis irgendwas zu zählen.
Aber rein praktisch werden die Programme dann natürlich zur Laufzeit,
also an irgendwelchen Stellen im Hauptspeicher abgelegt, je nachdem,
wo es passend platzfrei ist, beziehungsweise nach welchen Mechanismen da ist.
Also Betriebssystem und Lademechanismus dann arbeiten. Gut, also wie gesagt,
im Assembler-Code hat man aber eben typischerweise noch was, wie diese symbolischen Label.
Gut, Datentypen, dort hat man wirklich jetzt nur die ganz primitiven allereinfachsten Datentypen, die jetzt von der Maschine unterstützt werden. Wie gesagt,
sowas wie irgendwelche Aggregate, Arays, irgendwelche Strukturen,
Objekte gibt es da nicht,
sondern also wirklich nur sowas wie Fortlaufen adressierte Byte,
typischerweise dann eben sowas wie ganzteilige Daten,
also sprich von acht bis 64 Bits,
in denen ich dann eben entsprechend jetzt also meine Datentypen,
Integer, Long, Long, Long, was auch immer dann habe,
die können eben mit entsprechenden Wortlängen behaftet sein,
die können jetzt also positiv sein oder als Zweierkomplimentzahl interpretiert werden und Man hört eben letztendlich in die Bytes dann also auch noch sowas wie Text, also Asky oder eben typischerweise, also auch Unicode umgesetzt.
Wie gesagt, das, was uns jetzt hier immer begegnen wird,
sind erstmal nur rein arathmetische Werte. Das heißt also irgendwelche Sound bzw. Entager-Werte. Gut,
dann Glatkomoperation, die beiden I-Triple,
die 754 Formate finden da dann eben auch noch ihre Anwendung,
werden wir aber hier auch aus Zeitgründen,
also jetzt nicht weiter behandeln und im Prinzip Prinzip dann eben sowas wie Adressen,
hier immer noch als 32-Bit, Speicheradressen, inzwischen natürlich typischerweise dann 64-Bit-Adressen.
Gut, bei den Assembler-Befehlen,
welche Arten von Befehlen gibt es da, in den vorigen Kapiteln,
als es also quasi um Instruction Set Architect schon die Befehlsätze ging,
gab es ja auch immer schon mal so eine Einteilung,
welche Arten von Befehlen man grundsätzlich braucht,
also auch das findet sich dann hier typischerweise,
also zum einen sowas wie arathmetisch logische Befehle,
wo ich jetzt eben als hier Besonderheit bei Entel-Architektur eben sowohl auf Registern als auch auf dem Speicher,
gleichartig rechnen kann.
Nicht bei dem, wo ich letztendlich vor Weihnachten aufgehört hatte,
wo es also im Prinzip um Befehlssatzarchitekturen und gerade um diesen Unterschied zwischen Risk und Sisking.
Nicht da ist es ja so, dass man dann sagt,
okay, also bei ihm nochmal als Besonderheit bei Risk,
da habe ich eben eine typische Load-Store-Architektur, das heißt,
es gibt eben wirklich nur zwei Befehle,
wo ich aus dem Speicher in Register bzw. aus Register in den Speicherdaten transferieren kann und alles,
was arentmetisch logische Befehle sind,
können eben nur auf Registern rechnen
Bei dem, was wir hier jetzt sehen werden, ist es eben so,
dass sich eben mit all diesen Befehlen beliebig Register, Speicher oder irgendwelche Emediate-Werte benutzen kann. Gut,
dann hat man natürlich die üblichen logischen bzw. Schieberoperationen,
dann eben Möglichkeit zwischen Speicher und Register,
also Daten hin und her, zu bewegen und eben Mechanismen,
wo man im Prinzip dann den Programmfluss steuern kann,
das heißt also eben oder unbedingte Sprünge unter Programmaufrufe und dann eben noch Sachen,
die sich eben letztendlich so aus in Jarabbehandlung heraus ergeben.
Gut, Makros,
das wird jetzt hier weiter auch nicht genauer vorgestellt,
kann man sich aber eben Arthur erst mal so vorstellen,
ist dann erst mal nichts anderes,
dass einfach nur eine Folge von Assembler befehlen bzw. nichts anderes als eben irgendwie Zusammenfassung,
wo man dann sagt,
okay,
ich habe dann eben so eine Art fertiges Unterprogramm,
was dann eben entsprechend an mehreren Stellen benutzt wird
Gut, wenn man jetzt also diese sehr anspruchsvolle Programmierarbeit weiterverfolgt. Und diese Funktion Sam nimmt,
wie sieht da dann der Objektcode aus im Falle dieses X86 Assemblers und diesem Beispiel,
dass also aus Brandt und Höllerron ist,
sind das dann hier quasi 13 Byte Code,
die dann also quasi hier,
das ist dann eben die Entsprechung von diesem Label irgendwo eine Startadresse haben.
Und dann eben die jetzt AFA Hexadezimal kodiert, breitweise aufgelistet werden.
Und das sind jetzt eben unterschiedliche X86-Instruktionen, die dann entweder
nur Einbeit umfassen, teilweise aber auch zwei oder drei Byte.
Nicht, das ist also im Prinzip das, wo man sagt,
okay, also hier habe ich Push mit einem Argument,
hier habe ich Move mit zwei Argumenten.
Hier habe ich nur Return ohne ein Argument.
Und letztendlich, also dies Return wäre dann eben sowas,
was beispielsweise nur als Einbeit kodiert ist.
Bei den anderen sind das dann eben entsprechend
längere Befehle
Gut, das, was dann der eigentliche Assembler macht,
ist, dass er dann eben quasi diesen Assembler-Code nimmt,
daraus dann eben so eine Objektdatei generiert. Das ist einfach stumpf,
nichts anderes als eine 1 zu 1-Übersetzung von diesem mämonischen Assembler-Code,
den man noch irgendwie lesen kann in irgendwelche Strings aus Nullen und Einsen,
also sprich eine rein minäre Codierung.
Und das ist dann letztendlich das, was dann Bis auf
Programmbibliotheken dann eben letztendlich dann auch dem ausführbaren Code entspricht.
So war es hier jetzt natürlich noch nicht weiter behandelt wurde,
was wir hier in den Beispielen jetzt also auch noch nicht weiterhaben werden.
Dann nehme ich nachher dann oder ganz am Ende dann eben nur diesen Linkmechanismus.
Wie sieht das jetzt eben aus, wenn ich entsprechend mehrere Dateien habe? Ich war,
das machen Sie ja also an meiner Programmierung ja nun auch schon typischerweise so,
dass man sagt, okay,
ich schreibe jetzt nicht meinen gesamten Code in eine einzige Textdatei,
die ich dann also irgendwie da mal durchquodle,
sondern nicht typischerweise würde man jetzt eben sagen,
also man gruppiert irgendwelche und der Programme in einzelne Quellcode-Dateien,
die man dann eben entsprechend Arlsen übersetzt und dann eben am Ende entsprechend zusammenbringen.
Wenn ich das fehlt dann noch und das ist dann eben letztendlich,
was dann ganz am Ende der Linker bzw. der Binder auflöst,
wo man dann eben sagt, okay,
ich nehme jetzt eben meine einzelnen Objektdateien und wenn ich jetzt irgendwie noch statisch binde,
also auch noch irgendwelche Archivdateien von irgendwelchen Bibliotheksfunktionen und erzeuge mir daraus eben ein einziges Executable,
was sich dann eben letztendlich ausführen kann und dann eben im Prinzip mit Laufzeitbibliotheken oder mit dynamisch verknüpften Bibliotheken dann eben ausführen kann.
Gut, wie sieht beispielsweise dann eben so ein Assembler-Code aus?
Also bleiben wir jetzt hier bei dem zentralen Befehl von dieser Supersummenfunktion,
wo ich dann nämlich rechne,
TS gleich X plus Y. Nicht wo ich also quasi zwei ganze Zahlen addiere im Assembler,
entspricht dieses dann eben letztendlich Diese Einzeile hier,
wo dann im Prinzip zwei Vierbyte Entager addiert werden.
In dem Fall ist das dann eben so,
dass bei diesem Adbefehl, dass dann also ein,
wenn wir jetzt wieder in das letzte Kapitel zurückgehen,
beziehungsweise die eine Übungsaufgabe, die jetzt heute gerade rausgekommen ist,
dass dann eben quasi ein Zwei-Adressbefehl ist. Das heißt,
dieses eine Register hier ist sowohl als auch Ziel dieser Addition.
Das heißt also rein technisch gesehen, wenn ich,
das ist meinen C-Code umsetze, würde ich dann eben sagen,
X ist gleich X plus Y.
Und das ist dann eben hier entsprechend mein Ergebnis. So,
und die Operanten in diesem Fall sind dann eben zum einen das eine Register EAX.
Da ist dann quasi jetzt mein eine Operant X vorher irgendwie
durch irgendeinen magischen Mechanismus. Nein, durch einen anderen Assembler-Befehl,
der vorher in dem Coach dann reingekommen. Das zweite Argument,
also das Y wird in diesem Fall jetzt hier aus
dem Speicher gelesen und zwar von einer Speicheradresse,
die in EBP steht und wo dann also noch ein zusätzlicher Offset,
das ist diese Zahl 8 davor, hinzuaddiert wird.
Wie jetzt diese Adressierungsschemata aussehen, folgt gleich noch,
also auf, sagen wir mal, irgendwie die nächsten zwei,
drei Folien, wo das dann nochmal ein bisschen genauer vorgestellt wird,
war es also jede diese Notation entsprechend dann bedeuten sollen.
Gut, bei der Addition Beim X-86-Befehlsatz gibt es,
also es ist keine Unterschiede zwischen Signed oder Unsigned,
sondern die Worte werden jeweils immer gleichartig behandelt, beziehungsweise gleich addiert.
So, und wie gesagt, Ergebnis landet dann eben
auch wieder hier mit drin. So, im Objektcode,
also sprich, irgendwo hier in diesem String,
findet sich das Ganze dann eben irgendwo hier unter einer Adresse 146 in den 3 Bytes 03.
Das ist nämlich quasi der Code für dieses Ad-Long,
Nicht wo dann im Prinzip dann entsprechend dann die beiden Register,
beziehungsweise die anderen Informationen dann eben mit drinstehen.
Und das wäre dann in dem Fall diese Dreibyte,
die dann also im Prinzip in diesem Assembler-Befehl entsprechen. Gut,
das, was er in der Übungsaufgabe auch schon kurz angesprochen wurde,
beziehungsweise ja, ist dann eben letztendlich das, was man dann also immer Dissessempler nennt.
Das heißt, die Möglichkeit aus diesem Binärwust,
der letztendlich dort dann also einfach erst mal nur irgendwo steht,
eben auch wieder den mämonischen Code zu generieren.
Das kann man beispielsweise machen mit dem,
also jetzt hier beim Gnukomparler mit dem Befehl,
Object Dump minus D für Dissessemble,
wo ich dann eben im Prinzip jetzt also hier aus diesem Code quasi dann rausbekomme,
Startadresse und dann eben hier entsprechend diesen mämonischen Code,
wo man dann eben hinterher dann eben sehen kann, was das Ganze macht.
So, und das kann ich eben entweder,
also direkt auf dem ausführbaren Programm machen,
ich kann das aber eben auch noch auf einem,
einer Objektdatei machen, die jetzt also quasi noch nicht gelingt worden ist.
Funktioniert beides. Beim Ausführbarenprogramm würde man jetzt natürlich dann also
auch noch irgendwelche anderen Unterprogramme, beziehungsweise Aufrufe sehen,
wo ich da dann eben letztendlich schon soweit alles mit drin
habe oder eben verweise zu dynamisch gelingten Bibliotheken
Alternativ kann man dann eben auch mit irgendeinem Debugger arbeiten.
Also als Beispiel hier auch ein sehr,
oder extrem einfaches Tool, also GDB, Graphical Debugger,
beziehungsweise Graphical ist ja auch noch nicht so richtig.
Dafür gibt es dann also noch welche,
die dann also auch nochmal irgendwelche grafischen Oberflächen haben.
Also wie gesagt, der da nochmal irgendwie gucken möchte,
kann ich auch nur sagen, okay,
also entweder einfach selber auf dem Rechner mal ausprobieren,
sofern die Tools dort vorhanden sind hier bei uns
in den Poolräumen
Und der Linux ist die Software soweit. Da,
das einzige, was dort nicht funktioniert, ist also im
Prinzip 32 Bitcode, weil da fehlen ein paar Bibliotheken.
Oder man nimmt die bislang noch nicht nachgefragte virtuelle Maschine,
startet sich dort das Ganze und könnte das dann dort eben auch entsprechend mal ausmachen.
Und auch da gibt es dann eben die Möglichkeit dann,
also im Prinzip so eine Prozedur dann über so ein Dissessessembling,
eben letztendlich wieder in dieses Jahr umzusetzen.
So, und dieses Dissessemblieren,
das funktioniert im Prinzip eigentlich für allen ausführbaren Code,
für den gesamten Oder für den Prozessor, für den entsprechenden Befehlssatz.
Das heißt also, ich kann im Prinzip so ein Disassembling für
beliebige Programme machen. Das können also hier die Programme sein,
die jetzt also hier auf meinem Rechnen installiert sind und da Linux.
Ich kann aber auch, wie man also hier sieht,
eben auch irgendeine Excel-Datei von einem Windows-System nehmen und mir das dort eben genauso ansehen lassen,
weil natürlich auch die ja nichts anderes enthält als die Maschinenbefehle des Befehlssatzes,
die dann eben auf dieser Prozessorarchitektur abgearbeitet werden.
Und da gibt es keine Unterschiede,
also ob das nun Linux oder minus 10, 8,
7, XP oder was auch immer sein mag.
Das ist in dem Sinne identisch. Das einzige,
was da die Betriebssysteme eben wirklich unterscheidet, ist eben die Art und Weise,
wie laufen Lademechanismen ab, wie läuft das Speichermodell ab.
Das heißt also, wie ist Parameter Übergabe über ein Steak,
ist dann eben entsprechend ein bisschen anders gehandhabt und solche Sachen.
Aber im Prinzip der Sambler-Code funktioniert im Prinzip genauso.
Also ich kann, deswegen hatte ich ja hier
gerade eben nochmal vorheben Stickler irgendwie so
ein bisschen rumgehühnert
Gin, gin, gin. Also was man hier im Prinzip machen kann,
das ist, ich kann jetzt also hier also irgendwie ein... Nicht also ein Object-Dump von irgendeinem Programm nehmen,
was jetzt also hier beispielsweise auf dem Rechner ist,
was also dann im Prinzip ein Standard-Unix-Programm ist,
wo ich dann eben sagen kann, okay,
da kriege ich dann eben relativ längliches Dissessembly, weil das Programm,
also auch wenn das jetzt nur ein primitiver Editor ist,
aber eben dann doch schon ein bisschen mehr kann beinhaltet,
nicht wo man dann eben jetzt entsprechend da seinen Assembler-Code hat
und nicht und das Ganze ist eben in dem Fall dann...
Schön Eigentor
So, das ist immer das Schöne,
wenn man das jetzt nicht doch alles vorher ausprobiert hat.
Also das Ganze ist in diesem Fall jetzt ein 64-Bild.
Linux-Programm. Ich kann aber eben auch,
welches war das noch, notepad.exe.
Im Prinzip auch hier ein 32-Bit Windows-Programm nehmen.
Und also auch darauf
Das Ganze laufen lassen und also auch da hätte man dann
eben entsprechender sich die Umsetzung dann eben in irgendwelchen desassimplierten Coach,
der dann in dem Fall aber dann eben entsprechend jetzt,
also irgendwie das Windows-Programm ist. Und das Einzige,
wo die sich dann eben letztendlich natürlich unterscheiden ist,
also dass dann, also wenn irgendwelche Systembibliotheken oder irgendwelche Bibliotheken benutzt werden,
die natürlich also schon mal grundsätzlich anders aussehen,
grundsätzlich andere Funktionen haben und,
und,
Beziehungsweise hier bei dem Windows-Programm, dann hast du also von
so einem Notfall-Windows ist, ist sie also relativ wenig dynamisch gelingt.
Ansonsten würde man da auch noch irgendwelche Verweise in externe Bibliotheken finden.
Gut, ja, also das eben zu diesem Disassemblieren von Coach. Ja,
ansonsten das, wie gesagt, was jetzt also den Rest der heutigen Zeit,
beziehungsweise dann eben diese Woche und dann,
ich denke mal auch noch ein Teil der nächsten Woche,
folgt es eben grundsätzlicher Einblick zur Assembler-Programmierung.
Dazu werde ich jetzt also gleich mal ein bisschen was in
Andressierungsarten sagen, anschließend. Denkt man auch heute noch,
also die arathmetischen Operationen vorstellen und danach geht es dann eben nochmal darum,
wie man jetzt im Prinzip so was wie Sprünge macht,
was man dann eben braucht, um dann eben beispielsweise so was wie Programmstrukturen umzusetzen.
Das heißt also, wenn ich jetzt so,
wie es war,
wie eine If oder Case als Verzweigung habe in meinem C-Code,
wie daraus dann eben entsprechend der,
oder wie das Ganze dann eben wirklich jetzt,
also auf Maschinenebene
vom Assembler behandelt werden kann
Gut, wie gesagt, Einschränkungen hatte ich ja gesagt. Also wir arbeiten jetzt hier mit,
32-Bit-Datentypen und benutzen das Ganze dann eben eher so als,
ja, so ein bisschen riskmäßig,
also sprich Register und so weiter, aber eben vieles,
was man da so im Prinzip über den Befehlssatz eigentlich noch machen kann,
wird er sich jetzt nicht weiter benutzt und wie gesagt,
Einschränkungen dann eben auf,
die sind DAX bzw. den Assembler,
der ist also in diesem Fall von diesen Tools
hier letztendlich kommt
Gut, dazu nehmen wir also hier mal einen ganz einfachen Befehl.
Move Long hat zwei Argumente, Quelle und Ziel,
transferiert eben einen Longwert, also einen vier Byte,
also sprich 32 Bit, Wort, von Source, nach Destination.
Und sowohl Source als auch Destination, beziehungsweise gut,
bei Destination macht also dieses erste keinen Sinn,
können im Prinzip sein, Emediate-Werte.
Imediate heißt, in meinem Befehl habe ich jetzt wirklich
meine Zahl als numerischen Wert drinstehen
Wirklich wie so eine Kanstante. Wird hier in der Syntax eben immer dadurch gekennzeichnet,
dass das quasi ein Dollar als Brefix davor steht.
Und anschließend der Rest des Codes ist dann quasi eine Interpretation,
wie konstant ein NC. Das heißt,
wenn ich also hier Dollar 0x 4-0-0 habe,
dann ist es eben hexadezimal, 4-0-0.
Wenn ich Dollar minus 5-3-3 habe,
dann ist es dezimal minus 533.
Also das heißt, da kann man noch so einigermaßen anwenderfreundlich
da dann, also auch irgendwie dann seine Zahlen lesen.
Das heißt, man ist nicht zwingend darauf angewiesen hier,
also irgendwelche Hexerdezimalkodierung und Überlegung,
wie sieht das Ganze jetzt im Zweierkompliment aus irgendwie zu machen,
sondern kann dann eben auch wirklich mit Engineer-Werten arbeiten.
Gut, und das Ganze wird dann eben je nach Typ
in diesem Fall jetzt eben bei so einem Long dann eben in Vierbyte entsprechend kodiert.
Ansonsten kann das Argument oder können die dann eben auch sowohl
Source als auch Destination natürlich Register sein. Bei dem,
was wir hier betrachten, sind das acht Register,
die zur Verfügung stehen, wovon eigentlich auch nur sechs benutzt werden,
weil also diese beiden Grünen gekennzeichneten, ESP und EBP,
stehen da nämlich für Stackpointer und Basepointer.
Das ist etwas, was uns dann also noch begegnen wird,
wenn es darum geht, wie man Unterprogrammaufrufe im Assembler realisiert.
Da sind die dann nämlich quasi von entscheidender Bedeutung.
Das heißt, die sind in der Regel dann also da reserviert.
Das heißt, da bleiben also jetzt die war es gekennzeichneten sechs Register übrig.
Wird im Assembler-Code dann eben immer dadurch adressiert oder beschrieben,
dass man dann eben immer dieses Prozentzeichen davor hat.
Also mit Prozent irgendwas bezieht sich dann eben immer auf ein Register.
Oder Source oder Destination können byte im Speicher sein.
Das heißt, da habe ich dann eben irgendeine Speicheraddresse,
die sich, wie auch immer jetzt, also erstmal errechnet.
Und ab dieser Speicheradresse 4, also vier aufeinanderfolgende Byte,
wären dann im Prinzip hier Quelle beziehungsweise Ziel oder eben auch beides.
So, das heißt,
man kann dann eben mit so einem Move-Befehl letztendlich oder mit so einem Move long,
letztendlich hier folgende Kombination realisieren, dass ich so eben sage,
ich habe als Quelle ein Emediate fährt.
Ich habe als Ziel ein Register,
wäre dann beispielsweise sowas wie dieser Befehl, Move long,
Dollar 0X4, Register EAX, heißt also nichts anderes als diese,
dass dieser Hexer-Dizenalwert dann eben nach EAX kopiert wird,
beziehungsweise ERX eben mit diesem Wert geladen wird.
Ich kann als Ziel auch eine Speicheradresse haben. In diesem
Fall wird die dezimalzahlen minus 147 in den Speicher oder an
die Speicheradresse geschrieben, die mit ERX quasi referenziert wird.
Das heißt, ERX ist dann eben die Adresse,
unter der geschrieben wird. Das heißt,
also wenn ich das in C übersetze,
wäre quasi eher X ja nichts anderes als ein Pointer auf
den Speicher
Gut, dann kann ich sagen,
ich habe dann als andere Kombination Register zur Register,
also Move Long EAX EDX, also nichts anderes,
als dann eben hier entsprechend,
das Analogon & C. Register zu speichern, das heißt also,
ich kopiere den Inhalt des Registers und schreibe den an die Speicheradresse,
die in EDX drinsteht oder umgekehrt,
ich kann eben auch aus dem Speicher in ein Register,
also sprich von der Adresse, die mit ERX referenziert wird in das Register EDX hineinschreiben.
Das heißt also, diese letzten beiden Befehle werden, also ich sag mal jetzt,
da sind so rum gesehen nichts anderes als in diesem Fall ein Store,
in diesem Fall ein Load, nämlich Transferik ist der Speicher,
beziehungsweise Transferspeicher zu Register hier oben.
Gut, ja, hier nochmal zu den unterschiedlichen Adressierungsarten,
wie gesagt, im Mediat,
Dollar davor und dann eben irgendeine Zahl, Integer-Wert oder Hexadecimal,
Register eben immer gekennzeichnet durch dieses Prozentzeichen davor,
nicht und das bezieht sich dann eben auf das Register mit diesem Namen.
Bei der Speicheradressierung sieht das so aus,
dass ein Speicher immer dadurch gekennzeichnet oder eine Speicherzuckerfirma dadurch gekennzeichnet ist,
dass das von Klammern umschlossen ist.
Typischerweise habe ich dann also quasi hier den Namen eines Registers und dann die Klammer drumherum,
das heißt, ich greife an die Speicheradresse zu,
die quasi jetzt mit diesem Register referenziert wird.
Also das war gerade eben auch schon die entsprechenden Beispiele.
Und davon gibt es dann eben noch Verallgemeinerungen. Das heißt,
ich kann jetzt hier vor eben auch noch einen zusätzlichen Offset schreiben.
Das heißt, also ich nehme den Inhalt des Registers als Speicheradresse,
addiere dort noch diesen Offset D, steht dann für das Placement hinzu.
Und das ist dann eben die Speicheradresse, auf die ich darauf zugreife.
Beispiel wäre dann also was wie so ein Move Long,
8 Klammer auf, Prozent EBP-Klammer zu, Komma, EDX,
das heißt EBP-Basepointer wäre dann eben die Referenz auf den Speicher, auf die addiere ich noch acht drauf.
Das ist dann die Adresse, von der ich lese,
diesen Wert, kopiere ich dann eben in das Register EDX. Also das ist jetzt,
also ich hier schon weiß,
zum Beispiel schon mal ein Code gewesen, der im Zweifelsfall,
also irgendwo hier mit aufgetaucht ist, okay,
also hier ging das jetzt also irgendwie Displacement 8 und kopiere nach EAX,
aber das ist dann also etwas,
was man dann also typischerweise dann in einem Assembler-Code relativ häufig findet,
weil das sind Mechanismen,
die werden also quasi für die Parameterübergabe benutzt
Gut, dann, das erstmal nur so als erste Idee zu diesen Adressierungsarten.
Kommen wir jetzt also zu dem, ja,
also schon etwas größeren, aber auch nur Minimalgrößeren Beispiel,
wie jetzt also solche Assembler-Programme aussehen.
Und zwar, also nehmen wir jetzt diese Funktion Swap,
kriegt quasi jetzt zwei Speicheradressen oder zwei Referenzen
Auf den Speicher als Argumente, nämlich einen Zeiger auf X
und einen Zeiger auf Y. Das, was passieren soll,
ist, dass quasi X und Y vertauscht werden.
Und das, was man also hier letztendlich macht,
ist, dass man also hier zwei lokale Variable hat,
die entsprechend der referenziert. Das heißt,
also hier habe ich ja einen Call by Reference nicht,
dann kriege ich also hierüber dann eben entsprechend jetzt die jeweiligen
Werte und schreibe die dann eben quasi jetzt vertauscht auf diese Adressen dann eben entsprechend wieder zurück.
Das heißt also, wenn jetzt unter diesen Speicheradressen,
also irgendwie die Zahlen 5 und minus 20 standen,
steht dann eben hinterher minus 20 und 5 da.
Gut, in dem C-Code beziehungsweise diesen ganzen Sachen das,
was jetzt, also der Assembler daraus macht,
sieht das dann eben typischerweise so aus,
dass man das eben immer unterteilen kann,
in grob drei Teile
Etwas, was dann immer hier als Setup und als Finish bezeichnet wird.
Das findet sich eigentlich immer bei allen Arten von assembliertem Code, wo unter Programme aufgerufen werden.
Dazu werden wir dann also noch weiteren genauer sehen,
wie im Prinzip diese Unterprogrammaufruf über den Stack funktioniert.
Grob gesagt kann man eigentlich immer sagen, mit diesem Setup sorge ich dafür,
dass also quasi die Bearbeitung des Unterprogramms erstmal initialisiert wird und dass irgendwelche Parameter,
die vorher auf dem Stack abgelegt worden sind, dann auch passend darauf zugegriffen werden.
Dann habe ich hier quasi diesen inneren Teil,
wo dann eigentlich jetzt irgendwas berechnet oder irgendwas gemacht wird und mit dem Finish passiert dann nichts anderes als das,
was ich hier quasi initialisiert habe,
um jetzt also in meinem Unterprogramm zu rechnen,
wird hier alles wieder zurückgenommen und ich kehre dann zum Hauptprogramm zurück und typischerweise die letzte Anweisung ist dann eben sowas wie ein Return,
wo dann im Prinzip auf dem Top-off-Stack oder dem,
was dann hoffentlich also Top-off-Stack sein sollte,
dann nämlich die Rücksprungadresse liegt,
die dann eben in den Programmzähler geladen wird und
dann eben entsprechend weitergearbeitet wird in dem aufrufenden Programm
Nicht. Also egal, wie das hier innen drin aussieht,
weil da muss man auch mal sagen,
also das ist immer ein bisschen das Blöde.
Bei dem aktuellen GCC kriege ich diesen Code nämlich schon gar nicht mehr raus.
Also auch wenn ich das mit 32-Bit-Einstellungen mache oder so.
Also diese Beispiele wurden mit dem GCC 4,
5 oder irgendwie deutlich älter irgendwann mal vor einigen Jahren von
den Autoren, also irgendwie generiert. Inzwischen,
selbst wenn ich also noch irgendwie einen alten GCC auf meinem Rechner haben sollte,
sieht also der Code, der dort rauskommt,
also irgendwie anders aus in dem Zufolge,
findet sich das also nicht so wider
Deswegen eigentlich auch nur der Hinweis. Also es geht jetzt nicht darum,
genau so den Assembler-Code zu schreiben,
sondern wie bei allen Sachen in der Programmierung, kann man immer sagen,
also um einen Algorithmus oder um ein Verhalten zu beschreiben,
habe ich ja erstmal letztendlich beliebig viele Möglichkeiten, das zu kodieren.
Ich kann es geschickt kodieren, ich kann es vielleicht nicht so geschickt kodieren,
ich aber grundsätzlich hat man natürlich dann doch immer sehr,
sehr viele Möglichkeiten, sowas zu machen.
Genauso ist es dann eben auch so,
dass abhängig von Compiler-Version und noch stärker abhängig von irgendwelchen Optimierungseinstellungen für den Compiler,
also gerade dieser Teil hier und hier dann eben sehr stark unterschiedlich aussehen kann,
beziehungsweise das dazu führt, dass diese Unterteilung, so wie sie hier ist,
dann vielleicht auch gar nicht mehr so direkt da ist.
Ich deswegen auch nur der Hinweis, gut,
jetzt in einem, gerade ablaufenden Übungszettel war ja also diese
Sache mit dem Assembler Code nicht.
Also da geht es eben eigentlich auch nur darum,
dass man das einmal sieht.
Da gibt es jetzt nicht eine richtige Lösung und die Übungsgruppenleiter
fangen an
War in einem abgegebenen Code, also quasi irgendwie zusammenzustreichen,
wo das also nicht genau so aussieht,
sondern je nach Verwende im Compiler sieht der Code also beliebig unterschiedlich aus,
nicht, sondern da ging es eigentlich wirklich nur darum,
dass man sowas überhaupt erstmal so ein bisschen das erste Mal sieht.
Gut, aber generell,
Setup eben irgendwie Einstellung und was man also hier vielleicht auch schon sieht,
also ich habe hier sowas wie Push Long, EBP,
Move Long, ESP,
EBP und irgendwo hier unten habe ich ihn umgekehrter Reihenfolge,
quasi genau das Umgekehrte, also hier habe ich einen Move Long EBP nach ISP,
das ist dann quasi das Äquivalent zu dem hier.
Da wird das nämlich rückgängig gemacht, habe hier einen Pop von EBP,
das heißt, da lese ich das,
was ich hier auf den Stack gepusht habe, wieder zurück.
Und dann kommt eben der Rücksprung. So,
warum man das macht, wofür man das braucht, folgt dann gleich,
wenn es dann also wirklich darum geht, also wie funktionieren unter Programmaufrufe.
Gut, wie gesagt, das immer, Setup-Code, Finish-Code,
dann in dem Body wird dann eben die eigentliche Überrechnung gemacht,
in dem Fall, das, was hier passiert, ist,
dass im Prinzip in ECX und EDX diese beiden Argumente,
X und Y reingelesen werden.
Und zwar werden die dann eben über diesen Speichermechanismus von Basepointer
mit Offset 8 bzw. Basepointer mit Offset 12 geladen.
Das, was wir dann gleich sehen werden, ist,
dass sich unter Basepointer mit Offset 8 und Basepointer mit Offset 12
im Prinzip nichts anderes verbirgt als aufeinanderfolgendes Stack-Elemente,
in die die aufrufende Funktion vorher,
nämlich ihre Argumente reingeschrieben hat
Ich weiß, es geht ja darum, ich möchte ja dieses Unterprogramm aufrufen.
Irgendwie müssen diese Argumente ja übergeben werden. Und beim X680-Assembler
passiert das eben typischerweise so, dass die aufrufende Funktion,
die Argumente in umgekehrter Reihenfolge quasi in den Stack schreibt und anschließend dann eben einen Call ausführt,
Call führt dazu, dass eben die Rücksprungadresse auch noch auf den Stack gesichert wird und dann quasi dieser Code Swap hier angesprochen wird.
Das, was der dann eben macht, ist,
dass er quasi sich jetzt also den Stack passend initialisiert und anschließend von dem und der Adresse,
diese Argumente, die da vorher irgendwo reingeschoben worden sind, quasi zurücklesen kann. Das heißt,
also hier werden X und Y geholt, anschließend,
weil das ja erstmal auch nur Zeiger auf die eigentlichen Werte sind,
werden dann also quasi hier entsprechend dieser der Referenzierung dann eben für die beiden internen Variablen,
die beiden Register, E-A-X und EBX benutzt,
wo dann im Prinzip T-1 und Tenol quasi dann hier gelesen werden,
dass es dann eben hier entsprechend diese Referenzierung von den entsprechenden Werten nicht und anschließend werden diese Werte dann eben in umgekehrter Reihenfolge dann nämlich EAX,
was also vorher von der Adresse in ECX gelesen wurde,
in die Adresse von EDX zurückgeschrieben und umgekehrt.
Das heißt, damit habe ich dann eben genau dieses Vertauschen letztendlich realisiert.
Gut, hier ist nochmal die ganz allgemeine Form dieser Adressierungsmodi.
Das ist also auch was, was das jetzt für Übungsaufgabe 11 interessant ist,
wo man das braucht, beziehungsweise gut wird,
also auch auf dem Aufgabenplatz selber auch noch mal kurz beschrieben.
Nicht, wo man im Prinzip jetzt sagt, also ich habe,
also bislang oder das, was hier jetzt nur vorgestellt war,
war im Prinzip jetzt nur Register und Displacement,
die ganz allgemeine Form ist eigentlich dieses hier,
dass ich dann also im Prinzip habe, das Register,
das kann ich dann noch mit einem Wert skalieren.
Ich kann dann dazu noch ein Basisregister hin zu addieren und habe dann
eben noch zusätzlich mein Displacement, also das, was dieser Emediate ist.
Das heißt, die allgemeine Form ist in der Speicheradressierung,
sieht eben so aus, dass ich sage, In Media,
das ist dann eben noch ein Wert, der hinzuaddiert wird.
Zwei Register und ein Skalierungsfaktor und das Ganze bezieht sich dann eben auf eine Speicheradresse,
die sich quasi über diesen inneren Ausdruck heraus berechnet.
Nämlich Basisregister plus Indexregister, mal Skalierungsfaktor plus Emediate.
Da sieht man eben auch das, wo man sagen muss,
ja gut, also Das ist eben das, was,
also allein schon von dieser Adressierung nachher dann also eigentlich ziskrechner auszeichnet,
nämlich also ein relativ kompliziertes Adressierungsschema, also bei Riskrechnern,
also wenn man ein Mips oder Armarchitekturen sich ansieht,
da gibt es sowas nicht.
Da kann ich also wirklich nur direkten Speicher adressieren mit dem Inhalt eines Registers,
kann aber jetzt nicht noch plus Basis,
mal Skalierung und das und das rechnen,
was also hier mit drin ist.
Nicht, das ist eben eigentlich noch was,
was also aus dieser Cisk-Ära
heraus stammt
Was, aber vergleichsweise praktisch ist,
wenn ich jetzt nicht Hochsprache und nur optimierenden Compiler habe,
sondern wenn ich jetzt also wirklich auf Assembler-Ebene irgendwelchen Code schreiben will,
weil ich zum Beispiel an Datenstrukturen wie ein Ré,
dadurch verwalten kann, dass ich sage, ich habe hier die Basisadresse,
das ist also quasi der Zeiger auf das nullte Element.
Habe dann hier mein Ré Index in einem Register,
wo ich dann eben Plus 1, plus 2,
plus 3. Habt zusätzlich einen Skalierungsfaktor, der gibt mir nämlich an,
wie viele Byte, jedes Array-Element besitzt.
Das heißt, damit kann ich dann also statt plus 1 plus 2
dann eben meinetwegen plus 4 plus 8 und so weiter,
wenn ich jetzt also meinetwegen 4 Byte Pro Array-Element habe.
Und dann kann man eben noch mit diesem zusätzlichen Emedia
dann eben auch nochmal so einen zusätzlichen Offset draufsetzen.
Gut, also das ist im Prinzip das,
was im X86-Buffetsatz so möglich ist. Wie gesagt,
meistens wird das eben in diesen relativ einfachen Fällen hier benutzt,
die jetzt also sie unten beschrieben sind, beziehungsweise in dem,
was hier in den folgenden Beispielen ja auch immer darin ist,
hat man eigentlich typischerweise immer nur diese ganz einfachen Sachen.
Inhalt eines Registers und noch irgendein Displacement, womit ich damit rechne.
Ansonsten wird aber nicht viel mit rumgerechnet,
bis also noch da noch irgendwie auf so einen zusätzlichen Fall.
Gut, hier sieht man nochmal jetzt ein Beispiel,
wie dann also wirklich rein praktisch so eine Adressberechnung aussieht.
Also ich habe hier im Register EDX den Hexer Dezimalwert F000.
Im Register ECX 1-0-0. Und dieser Ausdruck 8
oder Hexalysmal 8 und Adresse oder der Speicheradresse dann eben von dem Register EDX,
würde sich dann eben quasi berechnen als F-0-0-0 plus 8 wäre dann eben diese Adresse.
Hier hätte ich jetzt quasi EDX plus ECX,
würde dann eben zu F-1-0-0 berechnet werden
Hier hätte man dann eben ECX dann nochmal mit diesem Skalierungsfaktor S versehen,
wo dann eben viermal dieses plus Basisadresse, also die Adresse F 400 werden.
Und hier hat man dann eben das Ganze jetzt mal ohne Basisadresse,
EDX mal zwei und ein Off-Set von 80,
wäre dann eben 1E 080 als Adresse, die sich daraus errechnet.
Gut, wie gesagt,
also das nur nochmal so als Veranschaulichung, also wie gesagt,
dieses allgemeine Schema ist eigentlich dieses Und wie gesagt,
so ein bisschen was mit Adressberechnung wird jetzt ja dann also auch in den Übungsaufgaben folgen.
Gut, zu den Assembler-Befehlen. Also wie gesagt,
hier haben wir jetzt also nur sehr einfache Varianten,
die dann eben auch nur auf dem Datentyp Long arbeiten.
Nicht deswegen enden die dann immer alle mit L.
Immer jeweils als zwei Adressbefehle.
Ad Long,
Sublong, Multiplikation
Links schieben, gibt es entweder als SA-LL-L oder SH-L-L. Gut,
beim Linksschieben gibt es keine Unterscheidung nach Arrethmetisch oder logisch.
Trotzdem gibt es dafür also irgendwie zwei Befehle. Machen aber
beide genau das Gleiche. Dann gibt es ein Rechtsschieben,
dort eben dann Unterscheidung zwischen Arrethmetisch und logischem Schieben,
exklusiv oder und oder was auch immer. Und wie gesagt,
die Berechnung ist dann eben einfach nichts anderes,
als dass man eben sagt, okay,
ich habe Quelle, hier die zweite Quelle,
beziehungsweise das Ziel und entsprechend dieser Ausdruck würde dann passend dazu berechnet werden.
Gut. Neben diesen binären Operatoren gibt es dann also noch unähre Apparatoren,
Inkrement, Dekrement, Negieren, oder Bedweise,
Bedweises invertieren,
das heißt also in C-Syntax wäre das dann eben Destination Plus
1,
Destination minus
Negieren wäre dann eben eine Arathmedische Regierung und Nord dann eben Mitvises invertieren, beziehungsweise wenn ich jetzt also bei den Kapitel so Zahlen, Darstellung bleibe, würde ich damit das einer Kompliment berechnen. Gut,
ein Befehl, der dann also auch hier gelegentlich nochmal mit auftaucht,
ist dann dieser L-E-E-L,
steht eigentlich also von einem Mämonik-Schreibweise her für Load Effective Address,
ist im Prinzip erstmal nichts anderes als,
dass ich Ein Wert nehme und in ein Register schreibe,
wird von den Compilern immer ganz gerne benutzt,
um jetzt also quasi nach diesem Adressierungsschema hier,
wo ja ich im Prinzip dann also auch schon Addition oder spricht zwar Addition beziehungsweise eine Multiplikation mit vorkommt,
auch schon irgendwelche arathmetischen Rechnungen zu machen, wo ich dann eben sage,
ich nehme es also quasi jetzt irgendwelche Inhalte von Registern addiere die und das Ergebnis landet dann eben in einem Register,
das heißt also anstatt jetzt,
also damit war es eigentlich dieser Name ausdrückt,
eine Adresse zu berechnen, wird das genommen,
um eigentlich einen Wert zu berechnen.
Gut, das ist jetzt also letztendlich dann also irgendwas,
was je nach Compiler sind, da es dann eben ist,
nicht nur das, hier jetzt nur als Erklärung eigentlich erstmal klar ist,
also warum nimmt man da jetzt nicht, also irgendwie ein AdLong und macht daraus jetzt zwei Befehle nicht. Also wie gesagt,
das sind die Sachen, wo man sagen kann,
es gibt da nicht den einen genau richtigen Code oder den Code,
den jetzt alle Compiler erzeugen,
sondern der eine macht auch aus zwei oder drei Befehle,
die das so und so aufbauen,
der andere sagt,
okay,
also wenn ich jetzt also hier irgendwie Inhalt eines Registers und einen Inhalten des anderen Registers
dazu addieren möchte und vielleicht noch irgendwie ein Offset und eine Multiplikation mit zwei oder ein logisches oder ein Linksschieben,
nicht also anstatt das jetzt eben in mehrere Befehle hiervon umzusetzen,
kann ich das stattdessen
in diesem Befehl machen
Gut, hier sieht man da zum Beispiel,
wo dann also letztendlich solche arathmetischen Funktionen benutzt werden.
Also wie gesagt, das ist jetzt hier erstmal so relativ unzusammenhängender Kot,
wo AFA jetzt nochmal diese verschiedenen Operationen benutzt werden.
Also nennt sich hier Arith,
hat drei Argumente und es sollen jetzt also hier im Prinzip
irgendwelche Ausdrücke berechnet werden, nicht T1,
die 2,
T4 und im Prinzip aus denen zusammen,
ergibt sich dann also irgendwie sowas wie ein Returnwert und letztendlich,
also hier, wie gesagt, Setup, Finish,
das ist im Prinzip immer das,
was man jetzt also erst mal braucht, um diese Unterprogramm-Mimik abzuarbeiten,
würden uns dann also quasi am Freitag noch weiter beschäftigen,
was dann also hier quasi mit diesem Push und Pop bzw. mit diesem Move und diesem Move sich verbirgt und hier innen drin in diesem eigentlichen Buddy,
das entspricht dann eben wirklich eins zu eins letztendlich,
diesen Befehlen, nicht wo ich sage, hier werden,
erst mal ein EAX und EDX, die ersten zwei Argumente,
in dem Fall nämlich X und Y geladen,
in diesem Fall dann also quasi über so einen Move-Long,
also wo ich dann also relativ zum Basepointe das Ganze dann eben quasi vom Stack hole.
Nicht dann habe ich also quasi jetzt X und Y hier,
beziehungsweise hier dran und mit diesem Load Effective Adress mag ich dann eben nichts anderes,
als dass ich eben zum Beispiel EDX plus EAX rechne,
das Ergebnis in ECX-Speicher,
das ist eben quasi genau diese eine Zeile hier.
Hier wird dann eben beispielsweise Z. Naja, das stimmt nicht. Also das Set plus T1,
das findet dann erst so unten statt,
wenn ich also im Prinzip dann noch das dritte Argument hole,
nicht,
aber im Prinzip lässt sich jetzt eben für jede dieser Zahlen
hier eine entsprechende oder entsprechende Assembler-Code,
also im Prinzip dann also in diesem Teil
letztendlich finden
Gut, das, was ich jetzt gerade lang und breit,
also jetzt aus dem Code abgeleitet habe, sieht man hier,
also jetzt nochmal so ein bisschen praktischer.
Hätte man vielleicht vorher mal vorspulen sollen. Also,
hier sehen wir es also nochmal angezeigt im Prinzip,
also das, wie der Stack aussieht.
Und zwar habe ich hier XY und Z irgendwie auf dem Stack,
habe jetzt also nach dem Call, also meine Returner-Adresse und Basepointer verweist,
dann quasi also auf den aktuellen Top-off-Stack.
Das heißt, immer mit diesen Zugriffen,
Basepointer mit entsprechendem Offset lande ich letztendlich dann immer genau bei dem,
was also die aufrufende Funktion hier auf den Stack geschrieben hat.
Also wo ich ja schon gesagt habe, also dieses hier,
der referenziert dann eben letztendlich mal ein X.
Hiermit hole ich mir meinen Y Y. Hiermit berechne ich dann
eben T1, also X plus
Nicht, dann habe ich hier als Berechnung Y mal drei,
das entspricht dann eben im Prinzip, also diese Zeile,
indem ich dann eben sage, EDX plus EDX mal zwei,
das war ja also quasi immer hier mit diesem Load Effective Adress,
dass man das jetzt quasi missbraucht,
um arenometische Operationen zu machen, das heißt,
damit mache ich da nichts anderes als dreimal EDX,
das Ergebnis landet dann eben auch ein EDX, das heißt,
da habe ich jetzt also drei Y drin stehen, anschließend wird.
Das Ganze dann hier mit diesem Befehl, Schiss,
um Vierbett, dann nach links geschoben, nicht das heißt,
also da Maus mache ich dann eben quasi statt drei AD,
dreimal EDX, gerechnet werden soll, 48,
mal EDX, 4 Bild nach links schieben heißt,
mal 2, mal 4 mal 8,
mal 16.
Das heißt, letztendlich mit diesen beiden Zeilen wird im Prinzip
48 mal Y berechnet. Also da sieht man dann auch immer schon,
also okay, wenn ich als Befehl Emo nehme,
könnte ich das mal mit allen Befehlen machen,
indem ich jetzt einfach quasi
einen konstanten Wert nehme
Also sprechen im Mediate. Hier ist das jetzt also relativ dreckreich
irgendwie auf dieses Lord Effective Adges und so ein Schieben umgesetzt.
Also auch da, das sind immer irgendwo so Sachen,
wo man sich fragt, okay, warum macht der Compiler das entsprechend so?
Gut, da kann man dann eben auch nur sagen,
ja gut, eine Multiplikation ist eben vergleichsweise aufwendig oder teuer,
kostet quasi jetzt mehr Zeit als Addition und Schieben.
Deswegen kann es eben sein, dass auch wenn es sich da sieht,
nur um eine Multiplikation mit einer Konstanten handelt, die Compiler,
dass also bei ihren Optimierungen eben eigentlich jetzt in sowas umsetzen und nicht in das,
wo man jetzt als Programmierer sagen würde,
das liegt eigentlich viel näher.
Gut, ja, und wie gesagt,
hier werden dann eben noch die entsprechenden anderen Werte berechnet und
letztendlich ergeben sich da dann eben jeweils zu den einzelnen Assemblerbefehlen dann eben hier die entsprechenden Interpretationen,
was dann dort quasi aus dem C-Code herauskommen würde.
Gut, logische Operationen. Hier jetzt also auch nur kurzes Beispiel,
wo dann also hier also beispielsweise irgendwelche logischen Operationen,
also im BitVises XO, ein Rechtsschieben, logisch,
Das Generieren so einer Maske und die Berechnung oder die Maskierung jetzt also eines Werts.
Also auch da kann man sagen, das wird hier innen
drin dann eben diese vier Befehle letztendlich umgesetzt.
Und auch da sind dann also durch den Compiler jetzt,
also irgendwelche Tricks da rein und Optimierung passiert,
nicht weil also zum Beispiel hier diese Berechnung dieser Maske.
Ich nehme ein einzelnes Bit, schiebe das um 13 Stellen nach links.
Das heißt, hätte jetzt Eins und 13 Nullen dahinter,
ziel davon jetzt noch sieben ab. Gut, das ist
eine Konstante. Das, was der Compiler daraus macht,
ist, dass er eben sagt, okay, das ist nur märscher Wert.
Daraus generiere ich mir jetzt eben ein Emediate-Wert, nämlich 8.185.
Und der taucht dann eben hier als Emediate-Wert, also auch direkt in dem Code auf. Das heißt,
das sind dann eben diese Compiler-Optimierungen,
wo dann letztendlich also aus irgendwelchen Eigenheiten von dem Programmcode,
in diesem Fall im C-Code, den der Programmier geschrieben hat,
dann eben entweder Emediates werden oder umgekehrt,
wie in einem anderen Beispiel,
wo also quasi aus Emediates dann also eben irgendwelche Additionen,
Schieberoperationen werden.
Gut, und auch da lassen sich dann eben immer direkt
solche eins zu eins Zuordnung zwischen diesen entsprechenden Befehlen und
den entsprechenden Zahlen jetzt in dem C-Code herausfinden.
Gut, also das jetzt nur so als Beispiele
wie Arrethmetische und logische Operationen
umgesetzt werden
Am Freitag geht es dann im Prinzip mit Kontrollfluss weiter,
also sprich Verzweigung, wie werden IF, wie werden schleifen,
wie werden mehrfach Verzweigung dann eben letztendlich umgesetzt.
Welche Befehle braucht man dafür? Und ja,
das soll es dann für heute gewesen sein