Gut, heute würde ich sagen, mit dem Teil zur Assembler-Programmierung soweit abschließen.
Das, was jetzt noch folgt, sind also nochmal ergänzend.
Ich hatte ja vorgestern schon angefangen, was zur Speicherprogrammierung zu sagen.
Das heißt also, wie im Prinzip bei Race aduziert werden.
Das werde ich heute also nochmal so ein bisschen ergänzen,
wobei das jetzt relativ kurz nur skizziert wird. Dort,
wie auch nachher in den weiteren Kapitel oder weiteren Teilen,
die jetzt also heute noch kommen,
geht es also immer erstmal nur so ein bisschen darum,
also grob
Eine Idee zu haben, was sich dahinter verbirgt,
wie bestimmte Strukturen quasi in Hardware umgesetzt werden,
beziehungsweise wie dann teilweise eben auch die Betriebssysteme, die Hardwarebetriebsmittel verwalten.
Gut, das sind jetzt damals nicht so spezielle oder so allgemeine Sachen,
dass man da jetzt also Angst haben muss,
dass in der Prüfung abgefragt wird. Das heißt,
da würden jetzt nicht irgendwelche Datenstrukturen dastehen und dann also gefragt werden,
also wie wird es auf welche Speicheradressen abgebildet, sondern Wie gesagt,
dies Jahr ist erst mal so ein bisschen allgemeines Grundverständnis.
Gut, bei Arrays hatte ich ja schon gesagt,
werden entsprechend der Größe der Elemente zusammenhängende Speicherbereiche reserviert.
Und der Compiler berechnet dann eben bei Array-Zugriffen
die jeweiligen Adressen bezogen quasi auf die Startadresse des Arrays,
was dann eben immer über diese Möglichkeiten dieser Speicheradressierung,
die der X86 Assembler da bietet,
da dann im Prinzip
umgerechnet werden
Bei Records, also sprich irgendwelchen zusammengesetzten Strukturen,
ist im Prinzip genau das Gleiche. Hier als Beispiel,
quasi so ein Rekord, der ein Indiger-Wert beinhaltet, ein Index,
ein Array in diesem Fall jetzt mit drei Elementen und ein Zeiger auf einen Indiger-Wert.
All da ist jetzt, wie gesagt,
immer mit der Prämisse 32-Bit-Architektur, nicht dem zuvorge,
ist dann also hier der Zeiger dann also auch nur vierbald lang und Es wird eben ein zusammenhängender Speicherbereich,
aluziiert und die Adressen beziehen sich dann eben quasi immer auch als Offsets dieser Startadresse.
Gut, wenn ich jetzt auf dieses erste Element,
also sprich den Integer-Wert oder diesen Index darauf zugreifen möchte,
wäre das also quasi jetzt hier ein C,
also eine der Referenzierung im Sambler-Code,
wird da dann eben auch eigentlich nichts anderes gemacht,
als dass der Sambler eben,
oder dass der Compiler eben weiß,
dieses Element liegt in dem Array quasi
Mit dem Offset Null an der Startadresse. Demzufolge wird dann eben auch nur entsprechend der Inhalt von dieser Adresse rausgelesen. Und ich habe dann demzufolge dann hier mein I gelesen. Bei diesem Array,
was sich da jetzt drin befindet, im Prinzip genau das Gleiche,
wie also auch sonst bei den Array-Zugriffen,
nur dass ich jetzt eben hier zusätzlich natürlich noch so ein Offset mit drin habe,
weil der Rekord quasi jetzt hier bei Offset Null beginnt,
dieses Array dann also nochmal mit dem Offset 4. Das heißt,
wenn ich auf ein Element, Index in dem Array drauf zugreifen möchte,
ist im Prinzip dann diese Adresse zu berechnen,
nämlich viermal Index, weil Basistyp, Indiginer, braucht vier Bite,
demzufolge dieses viermal Index, plus vier, das ist nämlich der Offset,
weil das Array ja dann quasi erst hier beginnt,
plus Basisadresse R. Darüber kann dann eben entsprechend jetzt also auf dieses Reihe hier darin zugegriffen werden.
Und hier unten sieht man, wie das Ganze
dann letztendlich im Assembler Code aussehen würde
Gut, bei diesem letzten Teil mit dem Pointer,
Strukturreferenzierung wird das Ganze jetzt quasi Affen und Nummer eins weitergetrieben. Da geht es darum,
dass dieser Pointer letztendlich mit dieser Funktion Set P auf das Element des Race zeigen soll,
dass den Index I besitzt,
was dann in C quasi zu einer derartig schönen übersichtlichen Schreibweise führt,
dass sich also quasi jetzt hier von dem Rekord P,
die Referenziere, dem soll was zugewiesen werden.
Und zwar die Adresse, deswegen hier das Kaufmanns-Ohr und von,
ja, dann Verweis in den entsprechenden Index,
der dann eben durch dieses erste Element I vorgegeben ist.
Und die entsprechende Berechnung mit Kommentar würde man dann eben hier im Assembler-Code finden.
Gut, das sind eben normalerweise dann auch Sachen, wo man sagt, Nicht.
Also gerade diese Adressberechnung, wie wird das letztendlich umgesetzt?
Das macht natürlich dann eigentlich immer der Compiler, der dann eigentlich ist,
also aus dem C-Code, den entsprechenden Assembler-Code macht.
Das heißt, da muss man sich schon in der Regel glücklicherweise nicht drum kümmern.
Hier auch noch mal wieder ein kurzer Verweis auf diese Ausrichtung von Datenstrukturen,
hatte ich ja im Zusammenhang mit Speicher und Speicherlayout auch schon kurz angesprochen.
Typischerweise sieht es eben so aus, dass um den Code
effizient zu machen und nicht über mehrere Wörter verteilt.
Also in der Regel die Datenstrukturen an Wortgrenzen ausgerichtet sind,
weil man sonst eben teilweise bei den Zugriffen entsprechend mehrere Wörter aus dem Speicher lesen muss, um jetzt also ein Beispielelement zu lesen.
Und das dann eben auch massive Vorteile bei der Verwaltung des Cashes hat,
weil es da dann nämlich im Worst Case auch dazu kommen kann,
dass dann also auf einmal mehrere Speicherzugriffe auftauchen würden und man das dadurch dann auch umgehen kann.
Deshalb wird es in der Regel auch so sein,
dass der Compiler teilweise in den Datenstrukturen auch entsprechende Lücken einfach mit einbaut,
weil das ist jetzt eben letztendlich wie bei vielen Themenstellungen in der Informatik immer irgendwo dieser Trade-Off,
dass wir sagen, möchten wir den Code,
das Problem, was auch immer, möglichst klein,
möglichst kompakt haben oder möchten wir das möglichst effizient haben in dem Sinne,
schnell in der Ausführung und dadurch, dass ich jetzt eben sage,
ich baue hier gegebenenfalls noch irgendwelche Lücken ein,
habe ich eben schnellere Speicherzugriffe,
als wenn ich jetzt also alles wirklich optimal bittweise im Hauptspeicher zusammen ablege.
Und das ist also Ausquetsche. Gut, hier auch nur noch mal kurz aufgezeigt,
also wie so typisches Alignment, also bei der IA32-Architektur aussieht,
nicht Charakter, da passiert in der Regel nichts.
Short werden dann im Prinzip auch immer nur auf entsprechende Zwarbaldstrukturen
Also sprich, und gerade, nein, gerade Wortgrenzen ausgerichtet, bei den Vierbadstrukturen genauso.
Mich war es hier dann eigentlich auch nur mit angedeutet,
ist, dass wenn dann die wahrigen Adressen,
was man da dann eben auch sieht,
dass das dann auch teilweise immer noch ein Betriebssystem abhängig ist,
also je nachdem, was dort dann mit dran ist.
Rein praktisch sieht das dann eben beispielsweise so aus,
dass wenn ich jetzt also hier so einen Rekord habe,
B, zeige auf so eine Struktur, die einen Charakter,
ein Ray mit 2, Into Jurs und einen Double-Wert enthält,
dass man das dann beispielsweise so ausrichtet,
dass also quasi diese Basisadresse von dem P jetzt an einem 64-Bit-Wort ausgerichtet wird.
Nicht demzufolge die Adresse dann eben vielfachs von acht ist,
um jetzt eben wirklich effizient auf den Speicher drauf zuzugreifen.
Nicht dann habe ich hier meinen Charakter,
das nächste Element wird dann aber jetzt eben nicht direkt dahinter gelegt,
sondern gibt es dann also hier eben noch ein bisschen Platz.
Vielfache von, also bei Entager dann eben typischerweise,
von 32-Bit, dann kommen die beiden Entager,
am Ende dieser Double-Wert und Double sind dann eben auch typischerweise
an den entsprechenden Wortgrenzen ausgerichtet.
Deswegen hier also
auch nochmal
wieder Sentalverschnitt
Gut, aber solche Sachen und also auch gerade diese Adressberechnung,
wenn ich jetzt eben innerhalb dieser Strukturen hier darauf zugreife,
wie eben schon angedeutet, passiert dann eigentlich durch den Compiler,
der das entsprechend berechnet.
Gut, dann abschließend zu dem Teil, statisch Speicheraluzierung,
wollte ich jetzt noch mal ganz kurz nur grob skizzieren oder motivieren,
was jetzt eigentlich mit so ein bisschen aktuelleren Programmkonzepten ist,
sprich das,
was wir inzwischen ja alle lernen,
ist objektorientierung
Alles, was sie vorher vorgestellt hatte, Beispiel C oder sowas,
ist ja immer noch eine klassische, einfache, emperative Programmiersprache.
Nicht, wo ich, also Datenstrukturen und Algorithmen habe.
Wenn ich das jetzt zu Objekten zusammenfasse, wie sieht es dort mit der Speicherverwaltung aus?
Da habe ich ja eben typischerweise meine Klassen,
wo ich dann eben die Daten und zugehörige Methoden zusammen koppel.
Ich habe Polymorphismus, das heißt, ich kann eben unter
einem Bezeichner einer Funktion an also unterschiedliche Basistypenarten mit bearbeiten.
Das wird dann dadurch geregelt,
wie ich dann also gleich noch ganz, ganz kurz zeige,
dass man dann also im Prinzip über so einen Mechanismus eindeutige Namen erzeugt,
um das dann also zu einem Programm zu lenken.
Viertel im Englischen immer als Neymangling bezeichnet. Mich dann brauche ich also entsprechende Metadaten,
die jetzt zur Laufzeit Typinformationen eben auch noch mitverwalten,
damit ich jetzt eben sagen kann, wenn ich jetzt eine Funktion oder Funktionalität irgendwo benutze,
auf welchem Basistyp gearbeitet und dann habe ich natürlich eben auch noch sowas wie Vererbungsmechanismen,
wo ich dann von übergeordneten Klassen dann eben irgendwelche Ableitungen habe.
Grundidee bei dem Ganzen ist dann eben auch eigentlich ähnlich wie vorher jetzt bei den Arrays oder Records oder den einfachen Datenstrukturen,
dass die so umgesetzt werden, dass sich dann eben ein Mechanismus habe,
der sowas wie diese eindeutigen Namen erzeugt,
dass es dann zusätzliche Pointer in den Klassen gibt,
mit denen ich dann eben auf die Typinformationen verweisen kann,
um dann eben zur Laufzeit über diesen Pointer nachzuprüfen,
wie ist der Basistyp und es gibt dann eben auch nur entsprechende Pointer in Tabellen,
wo dann die jeweiligen Funktionen hinterlegt sind,
um dann eben zu den jeweiligen Typen dann auch die passende Funktion zu nehmen.
In der Regel kann man sagen, auch da sind die Comparler inzwischen so,
dass das eigentlich mit einer vernünftigen Performance passiert und man da dann eigentlich auch effiziente Elementierung nachher mit rausbekommt.
Wie gesagt, die Details sind denen dann eben eigentlich auch etwas,
worum man sich glücklicherweise als Programmierer nicht kümmern muss.
Gut, wie sieht das aus jetzt erstmal von der Grundidee,
wenn ich jetzt sage, also objektorientiert, da habe ich ja Daten und zugehörige Methoden.
Wie gesagt, die Daten sind entsprechend angeordnet.
Zusätzlich gibt es dann eben für diese Run-Time-Type-Information eine zusätzliche Datenstruktur,
das heißt, da gibt es ja so einen Pointer,
der dann eben darauf verweist,
Um welchen Datentyp es sich handelt,
der dann eben auf die Basisklasse verweist und wo ich dann eben sowas wie Interface und Vererbungsinformationen mit habe.
Und es gibt dann eben noch einen zweiten Pointer,
der dann eben auf die jeweiligen Methoden verweist,
die dann in so einer Tabelle hinterlegt sind.
Und um jetzt zu einem bestimmten Typ dann die zugehörige Methode zu finden,
sieht das dann so aus,
dass dann eben Mechanismus greift,
der dort eindeutige Namen erzeugt und nachher daraus dann
die passende Methode gewählt werden kann
Polymorphismus, hier auch nochmal ganz grob, skizziert nicht,
ist ja eben, dass ich sage, gut,
ich habe jetzt im Prinzip sowas wie jetzt hier zwei Funktionen,
gleicher Name, unterschiedliche Typen.
Da habe ich nach dem ursprünglichen Konzept,
so wie ich das vorher immer vorgestellt hatte,
beziehungsweise bei dem,
was ich dann also nachher auch noch kurz zum Linker beschreiben werde,
eben folgendes Problem,
dass ich hier
gleichartige Bezeichner habe
Das würde dann nicht funktionieren. Das,
was man da dann eben typischerweise macht, ist dann eben dieses Namingling,
dass also quasi der eigentliche Funktionsname gebildet wird aus dem Briefix,
der also irgendwie eindeutig quasi über die Klasse generiert wird.
Dann der Name der Funktion und dann eben eine Typkennung,
die dann eben entsprechend auf den Entager oder den Flow-Typ passend verweist.
Und nachher im kombinierten Code würde man dann eben irgendwie sowas ähnliches wie diese beiden Bezeichner finden,
die dann eben einmal also auf diese Integra,
Float-Funktion verweisen und einmal eben auf diese Float-Funktion mit dem Float-Argument.
Um dann die Methoden aufzurufen, um jetzt also zu sagen,
woher oder um letztendlich diese Frage zu beantworten, weil er weiß,
ist die Methode, zu welchem Objekt sie gehört,
beziehungsweise wenn dann mit diesem Objekt gerechnet wird,
wie kommt man da also an die Exemplarvariablen heran?
Da wird dann quasi auch implizit so einen Trick eingebaut,
dass der Compiler this, also Java this,
dann also quasi als erstes Argument mit übergeben würde.
Das heißt, in der Funktion kann dann eben
direkt auf die Methode darauf zugegriffen werden. Das heißt,
dort kann, wenn jetzt eben dort gerechnet wird,
letztendlich sowohl auf die Daten quasi über diese der Referenzierung des X als auch auf die jeweiligen Methoden,
wo dann eben diese Tabelle mit den Funktionen hinterlegt ist, raufzugegriffen werden.
Das heißt also, so ein Aufruf,
wo ich dann also im Prinzip jetzt eine Methode Point 3D,
F, mit zwei Intiger werten, IU und J,
aufrufen würde, würde dann also letztendlich implizit dann zu sowas.
Und hier nur noch mal ein Beispiel,
wie jetzt also quasi NC Plus,
also mal so ein Code aussieht und dann hier der mit Object Damp
draus generierte Tal von dem Oder ein Teil von dem entsprechenden Programmcode.
Also ich habe hier eine Klasse Point 3D mit drei privaten Indiger-Werten.
Ich habe dann eben hier Point 3D hier mit den jeweiligen Zuordnungen.
Habe dann hier eine Methode Get X,
die mir dann also letztendlich den X-Wert liefern soll.
Im Hauptprogramm wird eben hier ein entsprechendes Objekt erzeugt.
Und es wird dann also mit so einem Print F,
also dann von dieser Klasse dann,
also Get
Aufgerufen und das war es dann also nachher im kompilierten Code irgendwo dann drin steht,
ist dann also im Prinzip hier dieser Teil,
der dann eigentlich dieses X zurückliefert,
was man aber eigentlich hier sieht und das ist eigentlich nur das,
was sie hier irgendwie zeigen wollte,
ist dann eben genau dieses Namingling-Verfahren,
dass es also hier quasi hier rüber dann,
also dieses Point 3D als Klasse,
der referenziert ist und dann eben hier diese Methode Get
X,
die dann eben so einen eindeutigen Namen liefert
Damit dran ist. Gut. Unterschiedliche Methoden, Vererbung,
das wird dann eben implementiert über diese Virtual Table,
da ist es eben Mechanismus,
wo der Compiler dann eben alle Methoden einer Klasse zusammensammelt,
das dann eben auch noch in eine entsprechende Datenstruktur schreibt und dann eben über die Referenz,
dadurch, dass also ja immer die,
der Verweis,
also auf die Methode selber mit drin übergeben wird,
kann dann eben quasi dann über This V-Table und
dann entsprechenden Offset dann immer eine Methode
ausgewählt werden
Und damit wird dann eben quasi ganz normal der Code in dem Assembler Code dann also umgesetzt und damit dann weitergearbeitet.
Nicht. Das sind also im Prinzip die Art und Weise,
also grob skizziert, erstmal nur die Art und Weise,
wie dann aber im Prinzip solche Objektorientierten Konzepte letztendlich dann also auch in Assembler mit umgesetzt werden. Hier auch nochmal kurz skizziert,
was sich dann eigentlich bei der Vererbung dort tut,
das Ganze ist dann ein Teil oder ein Ausschnitt,
also aus dieser Referenz Learns CPP,
wo es dann im Prinzip auch noch ein C Plus Plus geht,
hier nochmal so grafisch beschrieben nicht,
aber wo ich dann eben auch sage,
ich habe hier also quasi eine Basisklasse,
dann hier zwei abgelatete Klassen, die dann teilweise eben auch Funktionen erben,
teilweise aber eben auch die Funktion dann eben selber beschrieben haben.
Und dadurch, dass dann also hier das entsprechende Element aufruft,
geht das eben entweder hier drauf oder dann eben quasi per Vererbung in diese Basisklasse mit rein.
Das erstmal von der Idee her zu der Art und Weise,
wie solche objektorientierten Konzepte dann eigentlich auch compilermäßig umgesetzt werden.
Gut, das war jetzt eigentlich hier nochmal Zusammenfassung der Teil zu den Datenstrukturen,
wie die jetzt also im Speicher letztendlich umgesetzt werden.
Dann werde ich jetzt nochmal ein bisschen was zu dem Mechanismus des Linkers sagen,
beziehungsweise des Loders, wo es dann eben wirklich darum geht,
ich habe jetzt mal einen Code,
der wird vom Compiler übersetzt in Assembler Code aus dem Assembler-Code möchte ich dann ja also irgendwie ein ausführbares Programm machen,
beziehungsweise dieses ausführbare Programm dann eben ja auch in den Hauptspeicher bringen,
wo es ja dann also überhaupt erstmal nachher zu einer Maschine dann entsprechend abgearbeitet wird.
Da spielen dann eben Lingo und Loda entsprechende Rolle,
wo ich dann eben sage, wie erzeuge ich mir dieses Programm,
wie benutze ich da dann vielleicht irgendwelche Bibliotheken mit dazu
Das Loading ist dann quasi der Mechanismus,
wo dann also quasi aus dem ausführbaren Programm das Ganze in den Hauptspeicher gebracht wird,
wo dann eben noch zusätzlich mit hinzukommt,
dass sich dann eben auch noch sowas wie Shared-Libraries,
das heißt also Bibliotheken habe,
die dann eben von mehreren Programmen dynamisch geteilt werden.
Grundidee, wofür man das jetzt überhaupt erstmal braucht.
Bislang war das im Großen und Ganzen ja eigentlich
immer erstmal hier so zumindest in den Folien so beschrieben,
also in allen Übungsaufgaben war das ja auch schon anders,
dass man eigentlich immer sagt, das gesamte Programm ist in einer einzigen Quelldatei.
Nicht, die nehme ich dann,
werfe ich da so irgendwie einen C-Compiler drüber,
übersetze,
das hat hier
mein ausführbares Programm
Ein Nachteil ist natürlich, jedes Mal,
wenn ich also irgendwie an der Datei dann also eine kleine Änderung habe,
muss ich da also beliebig viel Code übersetzen,
war es natürlich demzufolge ineffizient ist oder einfach erstmal Laufzeit kostet,
wenn das Programm also hinreichend groß genug ist.
Und man möchte jetzt natürlich also auch nicht irgendwie eine riesen Datei bearbeiten mit Tausenden von Zahlen Coach,
was dann also irgendwie deutlich unübersichtlich ist,
wo ich dann also alles irgendwie zusammenpacke,
sondern in der Regel möchte man ja also seine Programmierprojekte,
also auch irgendwie so ein bisschen strukturieren,
modularisieren, wo man die dann eben auch Teile,
die man vielleicht auch woanders oder in anderen Projekten dann mal wieder verwendet,
dann eben auch entsprechend zusammenfasst.
Demzufolge hat man typischerweise natürlich in der Regel mehrere Quellcode-Dateien und grundlegende Funktionen seitens der Programmiersprache,
wie also zum Beispiel Marlock, Print,
F oder sowas,
was also da in C dann eben noch in den entsprechenden Bibliotheken mit drin ist,
müssen dann für ein ausführbares Programm ja auch noch irgendwie
mit hinzugepackt werden
Dazu als erste einfachste Variante dann eben das Statische binden,
wo ich dann eben sage, ich habe jetzt mehrere Quellen,
Dateien, die dann eben getrennt voneinander übersetzt werden.
Ich habe dann eben den Linker, der das Ganze dann
eben hinterher zusammenpackt und daraus dann eben das ausführbare Programm macht.
Jede dieser hier vom Compiler übersetzten Object-Dateien ist dann in einem typischen Objektcode,
der quasi dann als Relocateable gilt, Program Independent Objectcode,
wo natürlich noch irgendwo festgestellt werden muss,
wie funktionieren jetzt irgendwelche Aufrufe ausgetrennten Teilen, wie passen die zusammen.
Das heißt also, ich habe hier in diesem Teil A eben irgendeine Funktion,
die rufe ich hier in M auf, nicht,
wie behandeln die Mechanismen das und gleiches gilt dann natürlich,
also auch für irgendwelche Variablen, globale Variablen,
wie gehen die Programme daraus dann oder damit um,
um dann eben wirklich so eine einzelne ausführbare Datei zu bekommen.
Das sind dann eben letztendlich die Aufgaben eines Lenkers,
dann eben quasi diese einzelnen Objektdateien zusammenzunehmen,
da wird dann eben geguckt, welche Funktionen werden referenziert, externe Funktion
Dann werden eben alle Speicherreferenzen zusammengenommen und angepasst. Das können
sowohl Daten sein, die jetzt also in unterschiedlichen Talen liegen.
Das können irgendwelche Funktionen sein, eben auch sowas wie Bibliotheksfunktionen.
Und es wird im Code eben auch wirklich geguckt für die einzelnen Funktionen,
was ist dort an Funktionen drin, wird aber nicht benutzt.
Und das wird dann also sinnigerweise auch gleich eliminiert,
sodass man da dann also auch möglichst kompaktes Programm kommt
So, in der Regel passiert das dann eben automatisch über den Compiler,
der das dann also jetzt ja eigentlich dann auch über mehrere Schritte macht.
Also meistens benutzt man einen C,
das ist dann ja immer so, dass man eigentlich das,
maximal zwei Schritte oder sowas unterteilt nicht,
aber eigentlich sind das dann ja eben noch entsprechend deutlich mehr.
Es gibt also Pre-Pronzessor dann den eigentlichen Compiler,
dann den Assembler und nachher den Linker,
der dann eben also aus diesem Objektdateien dann also
ein ausführbares Programm macht
Die könnte man dann eben auch alle entsprechenden einzeln aufrufen,
über Kommandozahlenparameter, hat man also hunderte von Optimierungs- und Tuning-Möglichkeiten,
um da jetzt also an dem Code dann noch was zu modifizieren.
Gut, Vorteile von dieser Link-Methode,
also jetzt als erstes erstmal nur das statische Linken,
ist, die Programme sind dann eben letztendlich aus kleineren,
übersichtlichen Quellcode-Dateien heraus generiert worden und Es gibt dann eben auch noch so was wie diese Funktionsbibliotheken,
also gerade die ganzen Standard-C-Bibliotheken, irgendwelche erweiternden Bibliotheken mit irgendwelchen Grafikdatenstrukturen,
Grafikein- und Ausgaben und so weiter, sind da dann entsprechend auch hinterlegen.
Vorteile von dem Ganzen, wie gesagt,
ich muss letztendlich nur den Teil kompilieren, den ich selber ändere,
wenn ich jetzt also irgendwelche Bibliotheken nehme,
die sind im Prinzip schon fertig,
da muss ich also im Zweifelsfall nur mit entkludern,
also nach den jeweiligen Header-Dateien mit berücksichtigen,
muss mich dann aber da nicht weiter um den Code aus solchen Bibliotheken kümmern.
Und dadurch, dass dann eben Teile, die nicht benutzt werden,
in dem fertigen Binary dann eben auch nicht enthalten sind,
hat man dann demzufolge natürlich dann auch entsprechend kompakte Programme.
Gut, in unixartigen Systemen, also sprich hier bei so einem Linux,
sieht das dann eben so aus,
dass man das dann als ELF-Format,
Executable and Lincole-Format macht, beziehungsweise eigentlich gemacht hat,
weil inzwischen hat man da auch teilweise noch andere ausführbare Formate
Letztendlich ist aber auch dort die Grundidee, gleich oder ähnliches, genauso für Windows, Executables, also auch dort gibt es also entsprechend vergleichbare Mechanismen. Generell kann man sagen,
das ist also hier zumindest bei den Unixen,
also so ein einheitliches Datenformat, wo dann eben unterschienen wird zwischen den Objektdateien,
die noch zusammengelingt werden müssen, dann dem ausführbaren Binary,
das ist das Wasser, so ein Windows dann,
also irgendwie so ein Exe wäre und eben irgendwelchen Shared-Objektdateien,
das heißt Bibliotheken, die von mehreren Bainaries zur Laufzeit dann erst hinzugelingt werden können,
was dann eben auch nochmal den Vorteil hat,
was die eigentlichen Bainarisieren also erzeugt werden,
eben auch nochmal kleiner sind,
als wenn ich jetzt ein statisches Linken machen würde.
Vergleichbares hat man dann im Prinzip,
also auch auf Windows-Systemen, ausführbare Dateien-Echse und das,
was dann immer als DLL-Dynamic-Link-Library daherkommt, entspricht dann quasi dem,
was in Unix,
also diese Punkt-SO-Dateien letztendlich sind
Was ist in solchen Formaten an Informationen vorhanden? Wie gesagt,
also der Aufbau, wie das genau aussieht,
ist jetzt hier also erstmal gar nicht relevant,
aber sagen wir mal, dieses Grundverständnis,
was muss dort hinterlegt werden,
wie kriege ich also aus dem Ganzen nachher,
also mein ausführbares Programm ist dann eigentlich bei all diesen Formaten ähnlich oder vergleichbar zu sehen.
Gut, zum einen habe ich im Prinzip so einen Header,
der mir erstmal sagt,
also um was von der Datei handelt es sich,
ist das also jetzt Irgendwas,
was jetzt erstmal nur aus dem Compiler rausgekommen ist,
also eine reine Object-Datei.
Ist das eine Shared-Bibliothek oder ist das ein Executable?
Da steht da noch so ein bisschen was drin über die Art
der Architektur, sprich für welchen Prozessor ist das übersetzt?
Wie sieht das dann also auf dieser Maschine mit der Byte
Order aus und so weiter? Also diverse Verwaltungsinformationen. Nicht,
dass man da jetzt also nicht anfängt,
als jetzt irgendwelchen Code von irgendeiner ganz anderen Maschine bei sich laufen zu lassen,
beziehungsweise das wird dann also dadurch unterbunden lassen.
Also quasi der Loda feststellt, okay,
dieses Binary ist aber Leider für eine Spark-Maschine gemacht und ich
habe hier einen Intel-Prozessor, deswegen mag ich nicht. Gut,
dann gibt es also im Prinzip so ein bisschen zusätzliche Informationen in den Executables,
die dann also quasi vom Betriebssystem benötigt werden,
dann eben in dieser Text-Section der eigentliche Programmcode in der Data-Section,
die Variablen,
die statisch global definiert sind und
auch initialisiert worden sind
Dann gibt es einen Teil BSS, wo dann eben statische
Daten drin sind, die noch nicht initialisiert worden sind.
Übersetzung sollte eigentlich sein Blog Start by Simble. Oft sagt man
auch Better Safe Space. Nicht jedenfalls, also das die beiden Arten der Daten. Dann eine Symboltabelle,
wo letztendlich alle Bezeichner, die irgendwo drin vorkommen,
aufgelistet werden mit entsprechenden Off-Sets,
um nachher beim Laden des Programms beziehungsweise beim Linken,
dann eben relative Adressen als Verweise auf die einzelnen Objekte seien das und der Programmaufrufe,
das heißt also irgendwelche Adressen, die hier in dieser Text-Section liegen seien,
dass irgendwelche Datenelemente, also irgendwas hier,
du hast hier eine Data-Section-League,
dann eben entsprechend raussuchen zu können.
Gut, dann hier nochmal explizit jetzt eben für diesen Teil
des Linkens beziehungsweise nachher dann eben auch des Ladens Informationen,
die dann eben die Relocation beinhalten. Damit ist letztendlich ja gemeint,
dass ich sage, gut,
das Programm selber sieht von den Adressen ja erstmal so aus,
dass ich sage,
ich habe hier also irgendeine Startadresse null und gehe dann also
im Prinzip hier bis zu irgendeiner Adresse an und habe jetzt also beispielsweise meine Funktion A unter einer bestimmten Adresse null plus irgendeinen fiktiven Offset.
So, wenn das Programm jetzt in den Hauptspeicher geladen wird,
dann wird es also nicht zwingend irgendwo dort an der Adresse null starten,
sondern also irgendwo ganz woanders, weil da ist auch diverse andere Programme laufen,
die also auch alle in diese Art und Weise abgemacht sind.
Das heißt, ein Teil,
der hier rein kompilierten Adressberechnung oder der Adressen muss neu berechnet werden,
wenn das Programm zusammengelehnt wird und nochmal ein Teil muss wieder neu berechnet werden,
wenn das Programm wirklich zur Ausführung geladen wird.
Das ist das, was dann eben in diesen Relocation-Infos drinsteht,
eben einmal für den Programmcode,
für die Text-Section und einmal eben für die Data-Section.
Dann gibt es eben noch hier so ein Teil,
wo ein bisschen was mit D-Bug-Informationen mit drin steht,
sofern man das also in seinem,
bei seiner Kombination
mit angegeben hat als Paramittler
Gut, hier auch noch mal ganz kurz so ein Beispiel Quellcode,
also ich habe hier zwei oder ein Programm,
einmal in dieser C-Datei, M, Main,
im Prinzip also hier irgendwo was,
wo ich also hier ein Indiger Air mit einem Funktionsaufruf A initialisiere und das Programm dann mit einem Exit beende und in A,
dann im Prinzip hier, also diese Funktion,
die jetzt also hier irgendwelche statischen Werte hat,
dort an einen Retourenwert berechnet.
Und ja, das war's dann eigentlich. So,
was dann hier dann letztendlich ja hat,
sind eben als Funktion Main, A und diese externe Bibliotheksfunktion Exit.
Und ich habe dann eben vier globale Variablen.
E, EP, also ein Point of Inditor.
Und dann noch die beiden Werte X und Y,
wobei Die drei hier initialisiert werden, das Y,
hier erstmal noch nicht initialisiert worden ist.
So, wie sieht das jetzt aus, wenn ich
das Ganze jetzt übersetzt habe und linken würde? Das,
was der Linker letztendlich daraus macht, ist, dass er sagt,
okay, es gibt hier also erstmal so ein bisschen Systemcode,
der wird hier irgendwie mit reingeneriert,
dann nehme ich von den beiden Objektdateien den jeweiligen Code der Funktion.
Das heißt also, da ist eben die entsprechende Assembler-Befaile für
Main bzw. für A, das Ganze dann mit entsprechenden
Labeln versehen
Dann eben zusätzlichen System, Code, den ich beispielsweise
jetzt brauche, um diese Funktion Exit zu realisieren.
In der Data Section stehen dann jetzt die Variablen,
die initialisiert worden sind. Einmal ihm was zur Programmverwaltung,
dann eben diese drei Variablen,
E-E-P und X und die nicht initialisierte Zahl oder Variable Ented Y,
würde dann eben in dieser nicht initialisierten Sektion landen
Das ist also erst mal so grob die Idee,
wie das Ganze dann nochmal zusammengelingt wird. Hier sind die jeweiligen Teile
dann also nochmal kurz beschrieben, was sich dahinter verbirgt.
Hatte ich aber eigentlich soweit jetzt alles schon gesagt und vorgestellt nicht.
Also das heißt, hier sieht man eben auch,
wie dann eben wirklich dieses Zusammenspiel ist, also beispielsweise in dem Code,
der von dem Hauptprogramm Main habe ich eben hier eine Referenz auf ein externes Symbol A.
Und hier in A habe ich dann eben die Definition lokal dieses Symbols A nicht und Aufgabe des Linkers ist, dann hat beispielsweise dann eben das Ganze zusammenzubringen. Sieht dann so aus,
dass wenn man die Sachen jetzt erst mal übersetzt,
einzeln sich ansieht, das dann immer an den Stellen,
wo ich externe Verweise habe, also hier bin ich jetzt in der Text-Section,
also sprich den Programmencode oder in dem Assembler-Code von dem Hauptprogramm,
dass ich hier quasi zwei externe Verweise habe,
nämlich einmal auf die Funktion A und einmal auf die Funktion Exit und Für diese Verweise wird dann eben an diesen Stellen hier eben erst mal ein Platzhalter eingebaut,
der dann eben letztendlich durch den Link da,
wenn das Ganze zusammen gelingt,
wird dann eben durch eine entsprechende Adressberechnung ersetzt wird.
Genau das Gleiche gilt dann eben auch für irgendwelche Verweise auf Daten.
Also hier habe ich jetzt in dem Teil ARS,
also ja drei Variablen
Ähm, die hier lokal deklariert sind, eine externe Variable. Und das,
was dann also hier letztendlich noch passieren muss, ist,
dass ich also hier an den Stellen,
wo nachher also diese Variablen benutzt werden,
eben auch erstmal quasi hier für die Adressberechnung auch erstmal einen Platzhalter habe.
In diesem Fall ist es hier die Adresse Null.
Hier war das die Adresse.
Kommt jetzt wieder so ein Problem mit, diese Indianist dazu,
also man muss dann immer quasi jetzt diese vier Breite dann
immer von hinten nach vorne lesen jetzt für die Adressen.
Gut, hier genau das Gleiche, dann eben für A,
wo ich dann eben auch einen Verweis, also auf,
beziehungsweise hier jetzt die Data Section von A,
wo ich dann eben auch jeden Fall weiß,
auf die externe variable,
die ja dann also hier aus dem Hauptprogramm ist,
letztendlich habe
So, wenn das Ganze dann vom Linker zusammengelingt wird,
zu einem ausführbaren Programm, würden beispielsweise sowas bei rauskommen,
wo ich also dann hier auch erst mal eine willkürliche Startadresse habe,
dann hier den Code und alles,
was jetzt, also hier letztendlich rot ist,
wurde dann eben vom Linker entsprechend adressmäßig angepasst.
Das heißt, an der Stelle,
wo also die Funktion A aufgerufen wird,
muss ich dann eben einen Call machen auf die Adresse
Nämlich da, wo jetzt also A quasi beginnt,
was hier jetzt dadurch passiert,
dass also quasi dann Offset von 8 zu der Adresse folgt,
die dann also quasi jetzt hierhinter ist,
dass wir dann also im Prinzip 8048 53, 3,
4, 5,
6,
7,
8 plus 8 wäre 16 und das ist dann genau die
Ja, also darüber werden dann im Prinzip,
also über entsprechende Offsets, also dies wäre dann eben Offset,
also irgendwo in den Speicherbereich hier oben hin,
wo dann also die Bibliotheksfunktion für das Exit zum Beispiel drinsteht.
Darüber werden dann jeweils die Teile entsprechend angepasst und das gilt dann eben auch natürlich dann also für die Datensaktion,
wo dann eben auch die Verweise auf externe Variablen,
also das was Hier ja dann also quasi jetzt mit dem E passiert,
wo dann ja also quasi die Adresse von E genommen wird für die Initialisierung von AP,
wo das dann eben auch alles entsprechend umgerechnet wird.
Gut, dass die Art und Weise,
wie ein Linker jetzt also quasi ein Ausbildungsprogramm erzeugt,
was dort dann auch noch mit reinspielt,
ist dann die Art und Weise,
wie jetzt irgendwelche Symbole oder Bezeichner behandelt werden.
Da wird dann also auch immer noch unterschieden zwischen
starken und schwachen Symbolen im Prinzip
stark sind
Irgendwelche Bezeichnungen für Prozeduren, Funktionen und eben alle Daten oder globale Daten,
die eine Initialisierung haben, schwache Symbole sind nicht initialisierte Globale Daten.
Es gilt eben, jedes starke Symbol darf nur einmal auftreten.
Das ist dann letztendlich der Grund dafür,
warum ich bei dieser objektorientierten Programmierung dann also quasi mit diesem Naymay-Mangling arbeite,
um dann eben eindeutige Bezeichner für irgendwelche Funktionen beispielsweise zu bekommen.
Was dann eben passiert ist, dass dann auch schwache Symbole Quasi irgendwelche Variamen,
die nicht initialisiert werden, also starken Symbolen zugewiesen werden können.
Wobei sich der Linker dann eben ein beliebiges Symbol aussuchen darf.
Das kann Worst Case dann eben auch dazu führen,
dass es also zu irgendwie solchen Überdeckungseffekten kommt.
Also wenn ich jetzt hier zwei Quelldateien habe,
in denen jeweils eine Funktion PIs deklariert ist,
die beide identisch heißen würde ich, also vom Linker,
wenn ich also den Korpala stört das nicht weiter,
der übersetzt das noch brav zu.
Object-Dateien, aber der Linker würde dann eben quasi
den Link-Time-Error erzeugen und mir das also entsprechend anmerken
Wenn ich diesen Fall jetzt habe,
wo ich also hier in der einen Datei oder in beiden Dateien jetzt ein schwaches Symbol X habe,
dann ist eben auch die Frage, mit welchem X wird nachher,
wenn das zusammengelingt, ist gerechnet, wie handhabt der Linker das,
sind das dann also getrennte Elemente oder sind das jetzt für Sie gemeinsame Elemente,
dann habe ich also gerade hier bei schwachen Symbolen auch noch Probleme,
dass ich sagen kann, wenn ich also jetzt hier diesen Teil habe,
Hier ein Endiger X und Y und hier ein Double Wert X.
Und jetzt der Linker,
also irgendwie auf die Idee kommt zu sagen, okay,
ich nehme an dieser Stelle für dieses Symbol dieses hier,
kriege ich nämlich insofern ein Problem,
weil dieses jetzt hier jeweils vierbyt sind,
Double entsprechend doppelt so groß, Achtbyt,
das heißt mit einem Zugriff innerhalb von P2 auf X kann es sein,
dass ich mich also hier also auf einmal den Inhalt von Y,
weil der Lenker,
dass das auf die gleichen Adressen abgebildet hat,
Deswegen diese Anmerkung, dass man sich damit Y überschreiben kann.
Hier auch ein Problem,
also wie sieht das dann eben letztendlich aus,
wenn ich hier also starke Symbole habe und da das,
weil dann wird das also auf alle Fälle stattfinden.
Hier kann es stattfinden, hier wird es definitiv stattfinden.
Und hier habe ich dann eben auch Probleme,
dass ich hier ein starkes Symbol habe, hier den gleichen Bezeichner,
lokal,
der Linke kommt auf die Idee,
das aufeinander abzubilden
Und an der Stelle, wenn ich das hier benutze,
habe ich das aber hier vielleicht schon initialisiert. Gut,
das sind also, sagen wir mal, so einige von diesen Fehlern,
die dann also wirklich durch den Linker noch zusätzlich mit hinzukommen können,
wo man dann sagt, naja gut, das ist eigentlich etwas,
nicht, also wenn man sich diese Kotfragmente ansieht, was sie dann,
also vielleicht auch hoffentlich in SE schon mitbekommen haben sollten,
dass man also so einen Unsinn nicht macht.
Ich muss jetzt auch sagen, dass sie natürlich immer irgendwie notorische Beispiele,
wo ich mit irgendwelchen globalen Variablen arbeite,
nicht und die also in unterschiedlichen Modulen alles leicht benenne,
aber genau dann kann sowas eben auftreten.
Das heißt, da muss man dann eben wirklich sehen und aufpassen.
Also gerade bei der Programmierung, also entsprechend sauberen Code erzeugen,
wo ich jetzt eben nicht beliebig viele globale Variablen an sich verschiedenen Stellen im Programm irgendwo deklariere,
weil das ist ja immer genau das, wo,
dass sie alles letztendlich knallt.
Gut, hier auch noch mal kurz der Verweis Funktionsbibliotheken.
Da geht es ja eben darum, dass ich sage,
gut, häufig benötigte Funktionen muss ich nicht immer selber das Rad neu erfinden,
sondern das gibt es dann in der Regel alles schon in irgendwelchen Bibliotheken.
Die können zum einen erst mal statisch sein, das ist dann also das,
was in Unix-System dann immer irgendwie mit der Punkt A-Endung für Archiv aufgebaut ist.
Da sind dann eben vorkompilierte Funktionen zusammengefasst.
Und wenn ich dann in mein Programm zusammenbaue,
starte Schlinke und dann eben so eine Archivdatei mit hinzunehme,
wird dann eben der entsprechende Code dann quasi bei mir
in meinem Executable passend mit eingebaut
Das sieht dann also so aus, dass ich sage,
ich habe hier jetzt, also irgendwie Teile vom Programmcode,
daraus erzeuge ich dann jeweils Objektdateien,
habe dann eben hier noch irgendeine statische Bibliothek,
die hinter der Linke erzeugt, daraus dann eben ein ausführbares Programm,
wo dann eben, wie vorher beschrieben,
die Teile aus diesen Funktionen mit zusammengenommen werden und eben irgendwelche externen referenzierten Funktionen dann beispielsweise,
wie jetzt hier also aus der C-Basis-Bibliothek der LipC. Nicht und auch da gilt dann natürlich,
oder gerade da gilt dann natürlich dieser Teil,
dass eben hieraus nur die Teile genommen werden und in den Programmen landen,
die letztendlich hier auch vom Code her benutzt werden.
Gut, solche statischen Bibliotheken kann man dann eben auch entsprechend natürlich vorher zusammenbauen.
In der Regel kriegt man die dann also über das Betriebssystem mitgeliefert. Nicht,
aber ich könnte dann eben auch im Prinzip jetzt also
den Quellcode von der Lipzi oder einer mathematischen Bibliothek,
also der LipM irgendwie mehr ansehen und daraus dann gucken,
wie sind da die Funktionen definiert,
die würden dann letztendlich einzeln übersetzt und dann eben über dieses Archivprogramm,
also den Archiver, dann eben entsprechend zusammengebaut werden dann in so eine Bibliothek.
Gut, da sind dann typischerweise die verschiedenen Funktionen,
das Ganze dann eben je nach Bibliothek dann eben inhaltlich auch noch zusammengruppiert,
nicht also in der Lebzie,
also all das,
was dann in dem Sprachstandard noch irgendwo als Funktion definiert ist,
mathematische Funktionen dann
eben in der LebM und so weiter
Mit diesem Archivmechanismus kann man dann eben auch einzelne Funktionen dann auch austauschen.
Also ein inkrementelles Update. Also auch da lässt sich
dann auch was mit arbeiten. Gut, Standard,
Bibliotheken sind dann sowas wie LebC. LebM, die mathematischen Bibliotheken,
wo dann also auch eben zum Beispiel die ganzen Granalkommafunktionen
sowas hinterlegt sind und so weiter und so fort.
Hier nur als Beispiel,
wie man sich dann also zum Beispiel ansehen könnte,
welche Funktionen es in solchen Standardbibliotheken drin sind,
also mit AR-D für Test
Und dann eben dem Namen der Bibliothek und in diesem Fall
jetzt hier nochmal noch in so ein Sword reingepiped,
würde ich dann eben letztendlich sehen, was hier in der LipM drin ist.
Nicht, also die untere Zeile wäre dann also bei so runtoartigen Linux-Systemen,
Realitätssysteme oder Suse-Systeme haben dann also quasi als Bibliothekssuchfahrter noch so ein Lip64 und
dann nicht mehr hier diese Unterteilung mit denen hier drin.
Gut, sage ich Funktionsbibliotheken, wie gesagt, werden vorm linker entsprechend mit reingebaut.
Beim linken wird dann eben geguckt, sind alle Referenzen da,
fehlen Referenzen, würde es vom linker her eine Fehlermeldung kommen, komm zum Abbruch.
Was dabei beim Zusammenbauen von den Programmen dann eben auch immer noch wichtig ist,
ist die Reihenfolge der Module,
dass da dann also die Abhängigkeiten also auch stimmen und die Bibliotheken gehören dann eben ins Regel,
in der Regel an das Ende der Kommandozeile.
Bei den Unix-artigen Systemen sieht das so aus,
die Bibliotheken heißen dann eigentlich immer alle Lipp und haben dann also
irgendeinen Bezeichner Punkt A in der Kommandozeile des Linkers werden die
dann eben immer ohne diesen Lip-Refix angegeben mit dem großen L kann man da jetzt eben auch noch einen ganzen Suchfahrt mit angeben und also wenn ich jetzt also beispielsweise ein Programm habe,
wo ich, was jetzt zu einem ausführbaren Executable zusammenlinken möchte,
ich habe vier Quelldateien, A und B jeweils noch als C-Code,
dann schon zwei übersetzte Dateien mit Assembler-Code,
mit dem Großel, mit dem Suchfahrt sage ich,
ich möchte dann eben auch noch meinen aktuelles Arbeitsverzeichnis mit drin haben und dann soll eben auch noch eine Lip Bluetooth,
Lip Peace Red,
Lip M und Lip C mitbenutzt werden.
Dann wäre, so war es dann,
also hier die Zugehörige Kommandozeile für den Genug Compiler.
Gut, ich hatte ja schon gesagt,
der Loda sorgt dann eben dafür,
dass im Prinzip diese ELF-Module,
wo dann also quasi das Programm mit drin ist,
geladen und ausgeführt werden
Wo dann eben nochmal jetzt wieder für diese Adressen,
die dann hier drin sind,
wenn das dann eben wirklich ein Speicher geladen wird und entsprechende Adressesersetzungen mit stattfinden,
was dort dann eben auch noch mit passiert,
in der Regel ist das,
was man eigentlich meistens auch noch bei den Programmen macht,
dass sich also eigentlich ein dynamisches Linken mache,
das heißt nicht solche Archivbibliotheken statisch mit hinzulinke,
wo dann quasi der entsprechende Code beispielsweise für das Print F in den Beinary landet,
sondern da höchstwahrscheinlich noch beliebig viele andere Programme,
die jetzt gerade im Hauptspeicher liegen und irgendwie vom Betriebssystem ja dann auch irgendwann nochmal zur Ausführung gebracht werden,
auch noch irgendwo ein Print F benutzen,
wird es sich natürlich lohnen dann, also sowas dynamisch zu lenken,
das heißt, diese Bibliothek,
in der das Print F enthalten ist,
ist eben letztendlich nur ein einziges Mal im Hauptspeicher und der Verweis auf die Funktion wird dann eben entsprechend zur Laufzeit ersetzt.
Das ist dann eben das, was dies dynamische Linken ist,
wo dann das Programm auch zu einer Objektdatei kompiliert wird.
Die Bibliotheken dann aber erst beim Laden mit dazu gelingt werden und
sie dann eben letztendlich von mehreren Prozessen gleichzeitig benutzt werden können.
Das macht natürlich dann insgesamt den Code oder den Speicherverbrauch nochmal wieder deutlich kleiner,
weil ich dann also nicht 500 Mal,
also irgendwie die Routine für das Print F irgendwo im Hauptspeicher mit ablegen muss,
sondern eben wirklich nur ein einziges Mal die LepC.SO,
wo das Ganze dann drin steht.
Eine Frage
Eigentlich. Also, wenn Java wäre,
dann sowas wie bei meinem sich zum Beispiel an dir,
das Interface für Raiders interportiert, dann würde ich es implementieren.
Einmal darunter festgeladen, mit dir da,
ihr kann es nicht mehr schreiben, immer wieder neu.
Müsste ich jetzt also auch erstmal raten, also die Frage war jetzt,
also Umsetzungen Java und da ist jetzt natürlich wieder die Sache,
Java ist natürlich von der Idee her eigentlich erst mal Quote,
der eigentlich erst mal interpretiert wird.
Interessant wird, dass dann ja eben erst viele,
nur wenn ich sage, okay, ich kompiliere das mit einem Java-Compiler,
wo ich dann im Mechanismus mit drin habe,
wo ich dann also im Prinzip sowas habe.
Was natürlich in der Regel auch immer automatisch passiert, das heißt,
da muss ich mir ja auch nicht zwingend drum kümmern.
Gut, da muss ich jetzt zugeben,
dafür mache ich da jetzt also mit Java zu wenig,
um das jetzt also wirklich qualifiziert beantworten zu können,
Nicht, aber ich sehe jetzt irgendwie was Falsches. Also wie gesagt,
das hier sind jetzt eigentlich erstmal nur so Referenzen oder Beispiele dann eben wirklich auf Objektkote jetzt,
also irgendwie zusammengebaut und zur Ausführung gebracht wird.
Also wirklich, wo ich dann ein ausführbares Programm erzeuge,
was dann aus CC plus Plus Fortry ansonsten ein Code kommt,
sicherlich dann eben auch aus Java-Code, den ich dann also mit,
ja, war C dann also entsprechend dann also irgendwie kompiliert habe.
Aber wie da jetzt also genau Mechanismen wirken, Gut,
wie gesagt, das dann eben zu diesen Shared-Libraries,
liegen nur an meinem Speicher,
was es da dann eben auch noch gibt,
was es zumindest bei den Unixen eigentlich ganz praktisch ist,
ist, dass die Bibliotheken dann also auch noch in unterschiedlichen Versionen daherkommen können,
weil gelegentlich oder von Salsa Side wird dann also doch mal die Schnittstelle zu irgendwelchen Funktionen geändert und dadurch durch solche Versionierungen,
wo ich dann also zum Beispiel so eine OpenCVDA,
also es ist eine Bildverarbeitungsbibliothek, wo die dann eben entsprechende Versionsnummern hat,
dass wenn jetzt also eine andere Version ihre Prozeduraufrufe anders hat,
sich die nicht ins Gehege kommen können und trotzdem beide Varianten genutzt werden können.
Das ist also schon ganz praktisch. Gut, bei den Shared-Libraries,
wie gesagt, hier nochmal die passende Grafik dazu,
Quellcode mit dem Compiler übersetzen, mit dem linker,
letztendlich in das Aushöhere Executable,
was ich dann also beispielsweise über Kommandozeile oder irgendeinen anderen Staatenmechanismus
dann starte und beim Start wird es dann ja also ein Hauptspeicher geladen,
wo dann letztendlich der Loader bzw. der Dynamic Linker zuschlägt,
der dann eben noch die entsprechenden Shared-Libraries mit dazu nimmt
Wenn ich das Ganze jetzt auch noch um statisch gelingte Teile ergänze,
ergibt sich dann eben letztendlich so ein Bild,
wo ich dann also hier quasi meinen Quellcode habe,
hier irgendwas, was statisch dazu gelingt wird,
wo dann quasi Teil dieser Bibliotheksfunktion eben noch entsprechend hier in einem Executable landen und dynamisch gelingt,
werden dann eben Teile dann letztendlich zur Laufzeit mit hinzugenommen,
wenn das Programm gestartet wird.
Gut, hier im folgenden, auch noch mal kurz gezeigt,
wie dann also in diesem ELF-Format dann letztendlich die Speicheraufteilung aussieht.
Also ich habe ganz oben sieht man dann. Ja. Eigentlich dann,
das jetzt gleich hier war auf der übernächsten Folie dann,
wie das Ganze dann entsprechend aufgeteilt ist.
Ich habe ganz oben im Prinzip erstmal so eine Header-Information,
habe dann das, was ich jetzt hier immer als Mittwoch,
beziehungsweise letzte Woche dann also als Deck vorgestellt hatte,
mit drin, dann Hieb, wo dynamisch angeforderte Daten gespeichert werden,
dann eben statische Daten, den eigentlichen Code und anderer Verweilerung.
So, was ich bislang natürlich noch nicht erklärt hatte,
ist dann eben letztendlich, wie so eine dynamische Speicherverwaltung überhaupt erst mal passiert.
Grundidee ist, ich denke mal,
das soll dann aus SE hoffentlich klar sein, dass man eben sagt,
ich habe eben nicht immer für alle Daten oder Datenstrukturen statische Teile,
sondern viele gerade größere Datenstrukturen, die also wirklich nur zeitweise benötigt werden,
werden eben dynamisch generiert.
Und wenn ich sie also jetzt in meiner Berechnung oder meinem
Algorithmus nicht mehr brauche, sehe ich dann hoffentlich zu,
dass sie den Speicher wieder freigebe und damit dann nicht
mehr weiterarbeite
Das sieht dann NC beispielsweise so aus,
dass man das dann überansprechende Bibliotheksfunktionen, Malok, sich Speicher holen kann,
die ihn dann hoffentlich irgendwann später auch mit dem Free wieder freigibt,
in anderen Sprachen, in Java passiert sowas dann automatisch,
über einen Garbage-Collector, der dann auch dafür sorgt, dass geguckt wird,
welche Datenstrukturen werden noch benötigt und was wird dann entsprechend freigegeben.
Gerade in C, das ist jetzt auch das,
worauf jetzt hier immer diese Beispiele aufbauen,
kann man damit also beliebig viel Unsinn machen, billibig viel Programmierfehler einbauen,
einige von den Beispielen, die dann gleich folgen,
zeigen, dass also nochmal so ein bisschen anschaulich,
wobei das jetzt natürlich erstmal nur so grob die Klassen sind,
was man da alles falsch macht, aber eigentlich typisch immer das,
wo das Ganze letztendlich mit drin ist.
Und wie gesagt, also hier,
also aus dem Buch von Brian und Helleran,
der jetzt also dann also wirklich sagt, okay,
das ist nun leider die harte Realität,
also Speicher ist dann immer häufig das Problem,
war es dann also auch dazu führt,
dass Programme zur Laufzeit also irgendwie abstürzen
Weil das Ganze dann eben wirklich gerade bei dynamischen Datenstrukturen
beliebig viele Fehler hat. Ich meine,
praktisch sieht das dann nämlich auch da so aus, dass das,
was man nun immer relativ häufig an irgendwelchen Updates von irgendwelchen Programmen,
Betriebssystemen teilen, Bibliotheken, was auch immer, mitgeliefert bekommt.
Ich sah das ein Linux-System, sah das ein Windows-System,
wo es also dann immer nicht der monatliche Patch,
der am Dienstag ist
Ist eben genau das,
wo ihm irgendwo festgestellt wurde in der und der Bibliothek
oder in dem und dem Tal von dem Programmcode,
ist leider also irgendwo ein Fehler und es kann oder den und den Umständen auftauchen,
dass da also irgendwas mit dem Speicher passiert.
Und ja,
Worst Case kann dann eben irgendwie sowas mit dem Speicher passieren,
dass Programme aufgrund irgendwie bösartiger Prozeduraufrufe sich da also irgendwie Speicherbereiche
überschreiben und auf einmal ganz woanders im Speicher landen und dann dort weiterrechnen können.
Und dann hat man also
ein Virus geschrieben
Geht dann natürlich sehr stark ins Eingemacht. Inzwischen gibt es also auch seinen,
es ist Betriebssystems da, also noch einiges an Mechanismen,
die also dafür sorgen,
dass man da also auch noch zusätzlichen Schutz hat,
war also die Speicherbereiche vom Betriebssystem dann,
also auch quasi zufällig über so einen Verwürflungsmechanismus,
also zugeordnet werden und ein Programm nicht zwingt,
in einem logisch aufeinander oder adressmäßig aufeinanderfolgenden Speicherbereich landet,
aber nichtsdestotrotz,
da gibt es dann immer relativ viele Probleme,
die Grundidee davon werde ich also auch hoffentlich heute noch
relativ kurz skizzieren
Gut, bei Linux-Systemen sieht das in der Regel so aus,
dass man sagen kann, also Speicherbereich für ein Programm,
was jetzt ausgeführt wird.
Kann man sich immer so vorstellen. Also ich habe hier
einmal irgendwo in den höchsten Adressen dann das eigentliche Betriebssystem,
also wenn Colonel mit drin liegen, das Virtual Memory,
habe dann von da aus runterwandernd mein Stack.
Ich fuhr ja dann also das Programm immer quasi mit
diesem Stackpointer mit Bush
Und Pop beziehungsweise mit Call und Return letztendlich,
also hier dafür sorgt, dass das Stack dann hier entsprechend immer runterwächst.
Nicht nur haben. Nein, Programm, Text, Datensegment
beziehungsweise dieses BSS-Segment. Es gibt dann eben noch einen Tal,
wo es also letztendlich Shared-Libraries, Memory-Map eingeblendet werden in meinen Programmcode und alles,
was da dann eben dazwischen noch mit frei ist,
wird dann eben für Den Hiep benutzt, nämlich das,
was sich dann dynamisch mit einem solchen Malock-Befehlen an Speicher aluzieren kann.
Gut, wie sieht das mit dem Malock von der Grundidee her aus,
ist eben eine Funktion, wo ich dann eben sage,
die und die Größe, Speicherbereich möchte ich haben,
kriegt dann eben einen Pointer da drauf,
der eben die Adresse darauf beinhaltet, wenn das Ganze nicht geklappt hat,
wird eine Null zurückgeliefert,
mit Free auf einen Pointer kriege ich dann eben diesen Speicherbereich dann wieder freigegeben mit so einem Re-Alllog,
kann man dann eben hinterher dynamisch auch noch die Größe verändern.
Hier nochmal so ein bisschen Beispielcode,
wo also jetzt hier im Prinzip so ein Array generiert wird,
mit Werten gefüllt wird,
mit dem Re-Alllog entsprechend nochmal an dynamisch größer gemacht wird und damit dann rumgerechnet wird.
Also auf diesen Teil möchte ich jetzt mal kurz überspringen.
Zum Speicher-Layout, Problematik bei dem Mallock, Sehen wir jetzt
hier nochmal kurz was. Wenn ich jetzt also im Prinzip sage,
Malok 4, wird hier also ein Speicherbereich irgendwie aluziiert.
Anschließend mache ich nochmal mal auch 5, kriege da den
entsprechenden Speicherbereich mit dem nächsten Malok 6 habe ich dann diesen Bereich.
Wenn ich dann sage, Free P2 ist dieser Teilspeicherbereich hier Frage geben worden. Anschließend könnte ich mit einem Malok 2 hier wieder was nutzen. Was man dann aber sieht,
was dort natürlich auftaucht, ist als Problem, Fragmentierung,
was dann eben im Worst Case auch dazu führen kann,
dass ich sage, ich habe hier meinen Speicher,
aluziiere hier irgendwelche Bereiche, gebe hier was frei,
möchte dann anschließend Malok 6 machen und habe zwar theoretisch genug Speicher,
um diese Anfrage zu befriedigen, aber das,
was hier jetzt eben erst mal passiert ist,
wenn das der Reihe nach jetzt also aus der Programmabarbeitung so gemacht wurde und hier entsprechend frei gemacht wurde, das vom Speicherlayout dann hier so zegliedert ist, dass das Ganze dann eben nicht mehr passt.
Nicht. Das heißt, da kommt dann eben noch diese Fragestellung mit rein,
wie werden jetzt in solchen dynamischen Datenstrukturen freie Blöcke gefunden?
Dazu kann man sich jetzt unterschiedliche Strategien überlegen. Also hier werden
jetzt im folgenden nur noch mal so zwei mit verlinkten Listen dargestellt.
Würde ich also auch nur ganz kurz vorstellen. Man kann sich
da inzwischen auch deutlich effizientere Mechanismen, also über entsprechende Bäume beispielsweise, vorstellen.
Ich aber die Idee ist eben, dass ich sage,
wenn ich jetzt hier nach freien Blöcken suche,
dass ich hier letztendlich eben am Anfang der Liste immer sage,
wie groß ein jeweiliger Speicherbereich ist.
Und zusätzlich gibt es dann eben noch einen Fleck,
was dann sagt, ob er benutzt ist oder nicht, benutzt ist.
Hier ist das Ganze jetzt als einfach verkettete Liste dargestellt.
Man kann das dann also auch zu einer Doppelverkettetenliste machen und so weiter und so fort.
In dem Buch von Brian und Helleran gibt es dann also
eine relativ ausführliches Kapitel dazu.
Da gibt es dann also auch noch so eine Art Aufgabe,
wo man dann quasi jetzt sein eigenes Malock und Free irgendwie mal implementieren soll.
Wenn ich daraus jetzt also noch die folgenden Folien,
ich würde das jetzt aber hier entsprechend nur kurz skizzieren.
Also wie gesagt, ich habe jetzt hier für jeden Speicherbereich Größe,
Information ist er benutzt, ist er nicht benutzt.
Und kann mir damit dann also so eine verkettete Liste aufbauen.
Und wenn ich dann eben einen Malock machen möchte,
kann man dann eben anhand dieser Listenstruktur in dem Hieb letztendlich gucken,
wo ist noch was frei,
dann könnte man jetzt auch wieder unterschiedliche Strategien sich überlegen,
welchen freien Bereich nehme ich,
den ersten,
den ich finde
Vom Anfang des Heeps angerechnet. Das heißt,
dann gucke ich jede Reihe nach wirklich alles durch und nehme jetzt den ersten,
den ich hier noch irgendwie als Freien finde.
Oder wenn ich irgendwo schon gesucht habe und jetzt,
dass ich hier zum Beispiel die Smallock 2 gemacht habe,
dann also einen Verweis, quasi einen Pointer an dieser Stelle stehen habe,
den Nächsten, den ich finde, könnte dann natürlich,
wenn das also dann irgendwie hinten ist,
also ein anderer sein oder den, der am besten passt.
Also da kann man sich dann eben auch entsprechend unterschiedliche Strukturen überlegen,
wie man das letztendlich implementiert, nicht um das dann also noch ein bisschen effizienter zu machen.
Also hier ist das dann eben auch nochmal so als Beispiel im C-Code beschrieben,
wie ich dann also so einen Adblock dort implementieren würde.
Effizienter machen kann man das dann noch doppelt verketteten Listen,
wo ich dann also sowohl vorwärts als auch einen Rückwärtsverweis habe,
weil was man damit dann nämlich wieder sehr einfach machen kann,
ist, dass wenn ich letztendlich hier die Speicherbereiche habe und anschließenden Free von P3 mache,
habe ich diese Situation,
wenn ich anschließe, noch einen Frief von P2 mache,
dass ich dann eben diesen gesamten freien Speicherbereich jetzt wieder zu einem einzigen großen verkehrten kann,
was dann eben auch heißt,
dass ich dann also diese Verwaltungsinformation der Gesamtgröße dann eben auch entsprechend umschreiben müsste.
Das geht natürlich dann mit solchen doppelt verketteten Listen deutlich effizienter. Gut,
hier wäre dann also auch nochmal so als Beispiel,
wie dann eben letztendlich so ein Speicherlayout aussieht,
nicht mit den Ähm... Programmcode, dem Stack.
Hier oben wurde dann das Betriebssystem legen und dann eben diese dynamischen Speichel im Hieb.
Nicht also auch dazu kommen jetzt also hier nochmal so kleine Beispiele,
wie das Ganze dann letztendlich aussieht, während der Laufzeit.
Nicht froh dann also der Hieb hier irgendwie wächst und dann
also hier irgendwie diesen freien Bereich zwischen maximalgrößes Stack und dann
eben dem Daten und Textmengen füllen kann
Gut, auch hier ist das Ganze dann nochmal mit so einem Beispiel in irgendwelchen Adressen durchgespielt,
möchte jetzt aber auch eigentlich nur kurz überspringen,
um jetzt also die letzten knapp zehn Minuten nochmal ein bisschen was zu den Fehlern mit der Speicherverwaltung vorzustellen,
wo es also letztendlich darum geht,
was kann man hier alles falsch machen,
was man zumindest in C immer schön falsch machen kann in Java sollten,
also die meisten dieser Fehler nicht möglich sein.
Gut, zum einen sowas wie Irgendwie völlig ungültige Zeiger oder
irgendwelche der Referenzierung, also klassisch Scan-Eff soll,
in diesem Fall ein Dezimalwert einlesen und unter der Variablen Value abspeichern.
Das, was dort natürlich passieren muss, ist das Value ja letztendlich verändert wird.
Das heißt, ich müsste ein Call bei Referenz machen.
Hier habe ich aber jetzt nicht die Adresse von Value übergeben, sondern Value selber. Das heißt,
ich habe hier vergessen, das, was hier unten steht,
das Kaufmanns und davor zu setzen,
führt dann eben im Zweifelsfall schon mal dazu,
dass eben dann auch so Laufzeit, der klassische Segmentation Violation,
Speicher über Lauf oder wie auch immer das dann entsprechend übersetzt wird,
stattfindet.
Nicht dann auch so Fehler, lokale Variable.
Ich habe hier eine lokale Variable Value, Indiger Variable,
die gebe ich oder die Adresse auf diese Variable,
gebe ich hier als Returnwert aus dieser Funktion zurück
Sieht im Prinzip erst mal ganz in Ordnung und vernünftig aus.
So kann man ja auch rechnen. Das Problem ist,
dass hinterher, wenn die Funktion beendet ist,
wenn ich in einem aufrufenden Programm bin,
die Speicherverwaltung natürlich auch sagen kann, wunderbar.
Dieser Teil hier von dem Value ist letztendlich frei,
wird, das hatte Compilern gemacht für irgendeine andere Variable genommen.
Das, was ich hier zurückgegeben habe,
ist aber immer noch eine Adresse da drauf und man liest dann
eben auf einmal mit völlig falschen Werten und rechnet damit weiter.
Das sind also immer so schöne Effekte wo man sich da
dann also wirklich den Code zerschießen kann.
Hier habe ich sowas wie nicht initialisierten Speicherlesen. Das heißt,
ich erzeuge mich hier mit Marlok im Prinzip einen größeren Speicherbereich
und benutze den dann hier direkt, womit da dann also gerechnet wird.
Und also auch das kann natürlich dazu führen,
dass dort dieser Speicherbereich vor von anderen Unterprogrammen benutzt wurde,
irgendwelche Werte drinstehen,
völlig willkürlich und man da dann eben
völlig falsche Sachen hat
Nicht dann solche Effekte wie falsche Größenwerte, bei Malock,
wo ich dann also im Prinzip sage, ich habe,
erzeuge mir hier einen Array von Zeigern,
was dann wieder auf ein Array zeigt.
Im Prinzip mache ich hier dann also so einen Malock,
N-Mile-Size of Indiger,
das ist P und für jedes Element P mache ich dann
wieder einen Malock auf ein entsprechendes Array mit der Größe
So, und das, was dieses P letztendlich ja darstellt,
soll ja ein Array von Zeigern sein.
Die habe ich jetzt hier mit der Wortgröße für Integr aluziiert. Auf einen 32-Bit-System funktioniert das Ganze,
wenn das Integration 32-Bit ist und der zeige, dann 32-Bit,
wenn ich den Code auf dem 64-Bit-System übersetze oder laufen lasse,
kriege ich ein massives Problem,
weil der Zeiger dann nämlich eigentlich doppelt so groß ist.
Nicht, also das wäre dann also falsche Größe,
dann solche Sachen, nicht klassisches Beispiel, Index-Berechnung,
dass man sich also irgendwo verrechnet, Ich habe hier ein Elemente,
lass dann aber hier meine Schleife, wo ich durch die Elemente gehe,
leider nicht en mal, sondern N plus eins mal durchlaufen.
Und auch da habe ich dann natürlich am Ende dann also auf einmal
irgendwie was Falsches oder Greif eben irgendwo in den Speicher hinein,
wo ich eigentlich vorher das nie initialisiert habe oder nichts Vernünftiges drin stehen habe.
Gut, und was dann eben klassischer Fehler oder klassisches Problem ist,
ist eben sowas, wo es dann also darum geht,
dass ich eben irgendwelche Puffer, gerade bei Charakter,
also bei Strings, mit einer bestimmten Größe versehen habe,
hinterher in der Stringbehandlung, mich nicht an diese Größe halte.
Beispiel ist, ich habe hier ein Array von acht Zeichen,
mache ein Get String auf dieses Array und lese jetzt hier
aber neun Zeichen und was passiert dann?
Nicht, also damit kann man dann entsprechendes Probleme machen,
das ist eben gerade ein Fehler,
wo ich dann also in Verbindung mit Unterprogramm aufrufen dafür sorgen kann,
dass ich mir also auf einmal irgendwelche Behandlung in der Stack-Berechnung beziehungsweise in dem Unterprogramm Aufrufen kaputt mache.
Gut, dann solche Sachen, Speicherbereiche, aluziiert, freigegeben.
Nochmal Frage geben. Nicht, was kommt daraus? Speicherbereich freigegeben,
hier rechne ich wieder damit. Das sind also Fehler,
die, sagen wir mal, syntaktisch vom Compiler nicht angemeckert werden,
die aber natürlich eigentlich grobe Programmierfehler sind,
wo man einfach sagen kann,
okay,
das ist also
völlig unsinniger Kot
Ich solche Sachen wie Speicherlex, dass es eben Speicher wird mit Maloc aluziiert,
wird aber nicht Frage geben oder nicht richtig freigegeben.
Und nach dem Rücksprung bleibt dann also hier bei diesem Unterprogramm
dann eben der Speicher belegt. Und nicht man wundert sich,
dass das Programm, wenn es lange genug läuft,
eben immer mehr Speicher frisst und dann also vielleicht irgendwann es
dann mal abstürzt oder was weiß ich.
Also gerade Speicherlex sind also auch immer so ein häufiges Problem,
gerade bei irgendwelchen Programmen oder Prozessen,
wenn die entsprechend lange laufen,
da also
solche Fehler drin sind
Kriegt man das dann also entsprechend mit. Hier auch nochmal so ein Beispiel,
wo ich dann also im Prinzip hier mit dem Malok eine Liste generiere,
damit dann rechne, dann hier ein Free von Head mache,
damit aber nicht die Liste oder den halluziierten Speicherbereich freigebe,
sondern wirklich nur das erste Element, was dort aluziert worden werde.
Gut, abschließend,
jetzt noch mal kurz dieses Beispiel mit dem GetS aus der Standard-C-Bibliothek,
nämlich das, womit man sich schon als wunderbar irgendwie Programmcode selber überschreiben kann,
implementiert ist, dass also in der Art und Weise,
dass ich also quasi hier ein Zeiger auf einen String bekomme und es werden dann eben so lange Elemente gelesen und damit dann zurückgegeben.
Und ich muss dann eben ein entsprechend großes Element oder
entsprechend groß im Bereich vorher aluziiert haben und übergeben haben.
Hier als Beispiel, ich habe hier eine Funktion Echo,
vier Charakter RA, mache dann eben Get Ass übergebt das als Argument.
Und das mit den vier Carractern ist natürlich also vergleichsweise optimistisch,
weil das Problem ist, diese Funktion geht es,
überprüft jetzt eben nicht, wie lang die Eineingabe ist.
Die benutzt so, und dieses Echo benutze ich jetzt,
also hier in meinem Hauptprogramm, was kann da alles passieren,
wenn ich das laufen lasse, irgendwas eingebe,
was also entsprechend kürzer ist als die vier Elemente.
Kein Problem,
das Programm läuft, wenn das entsprechend länger ist,
kriege ich dann also hier bei den Unix-Systemen dann eben so ein Segmentation Falt,
war ich dann eben da eine Adresse habe,
die nicht in Ordnung ist und dass Array den Stack überschrieben hat.
Das Verfahren dazu, was dort passieren kann,
ist also jetzt hier auf den nächsten Folien noch kurz skizziert.
Ich habe hier mein Hauptprogramm, habe hier jetzt meinen Speicher angelegt,
der liegt dann also irgendwo hier in einem Stack in der aufrufenden Funktion,
wurde im Prinzip hiermit als Adresse berechnet.
Die Funktion wird aufgerufen, dort landen dann eben die Werte,
die eingelesen werden und wird wieder verlassen
Was kann dort jetzt eben passieren, wenn ich einen String eingebe,
der kurz genug ist, habe ich hier unter diesen Adressen dann eben mein String.
Die Funktion wird verlassen und in dem Hauptprogramm wird dann eben entsprechend weitergerechnet. Ein Problem bekomme ich,
wenn ich jetzt zu lange Eingaben mache und also hier als Eingabe 1,
2, 3, 4, 5 mache,
dann fange ich nämlich hier auf einmal an,
was die Funktion jetzt nicht abprüft,
dass ich hier Nicht nur den reservierten Speicher-Bereich,
den ich übergeben habe oder vorher aluziiert habe, überschreibe,
sondern dass die SketS dann also auf einmal hier in den nächst höheren Worten,
in dem Stack einfach weiterschreibt.
Das heißt, damit fange ich mal an,
hier auf einmal in meinem Gesicht vorher gesicherten Basepointer zu überschreiben,
der zeigt dann hinterher irgendwie in die Wallachai.
Und noch schlimmer wird das Ganze,
wenn ich den String, den ich dort reinschreibe,
ist eben noch größer mache und der also anfängt,
hier im Stack
die Rücksprungadresse zu überschreiben
Was also hiermit jetzt angedeutet ist,
weil das ist dann nämlich letztendlich der Mechanismus,
wie man im Prinzip jetzt bei irgendwelchen Viren dafür sorgt,
dass quasi bei solchen Programmen aufrufen und bei solchen unsicheren Funktionen wie diesem Get Ass, dann,
wenn dort also quasi ein zu kleiner Speicherbereich angegeben ist und dort ein entsprechend langer Code eingelesen wird,
man sich also auf einmal in dem Tal des Stacks was überschreibt,
eigentlich gar nicht mehr zu dieser Funktion gehört und nach dem Rücksprung dann also auf einmal,
wenn ich das also hier passend platziert habe,
an irgendwelchen Adressen weitergearbeitet wird,
wo dann der Shartcode liegt.
Nicht, das ist dann also das, womit im Prinzip das,
was man so als BAFA-Overflow kennt, letztendlich arbeitet.
Gut, das jetzt hier nur kurz skizziert,
zu einer Art und Weise, wie das funktioniert.
Damit dieser Teil Assembler-Ebene fertig. Nochmal danke für
die Geduld und nächste Woche geht es dann entsprechend weiter