Apropos Programmbetriebsmechanismus von Hello World

小云云
Freigeben: 2023-03-17 15:52:01
Original
1446 Leute haben es durchsucht

Als wir anfingen, mit dem Programmieren zu beginnen, war das erste kleine Projekt, das wir abgeschlossen haben, „Hallo Welt“. In kurzer Zeit konnten wir seine „Hallo Welt“ in dieser Sprache schreiben. Aber schauen Sie nicht hin, es sind nur ein paar Buchstaben. Allerdings können die meisten Menschen den internen Funktionsmechanismus des einfachen Programms „Hallo Welt“ immer noch nicht erklären, deshalb werden wir Ihnen heute den Funktionsmechanismus des Programms erläutern.

Hallo Welt Wie werden diese Meldungen auf dem Monitor angezeigt? Der von der CPU ausgeführte Code unterscheidet sich definitiv von dem Code, den wir im Programm schreiben. Wie verändert sich der Code, den wir geschrieben haben, zu Code, den die CPU ausführen kann? Wo ist der Code, wenn das Programm ausgeführt wird? Wie sind sie organisiert? Wo werden die Variablen im Programm gespeichert? Wie erscheinen Funktionsaufrufe? In diesem Artikel wird kurz erläutert, wie das Programm funktioniert.

Der verborgene Prozess der Entwicklungsplattform

Jede Sprache hat ihre eigene Entwicklungsplattform und die meisten unserer Programme werden hier geboren. Der Konvertierungsprozess vom Programmquellcode in eine ausführbare Datei ist tatsächlich in viele Schritte unterteilt und sehr kompliziert. Die aktuelle Entwicklungsplattform übernimmt jedoch nicht nur den Komfort, sondern verbirgt auch viel Implementierung Details. Daher sind die meisten Programmierer nur für das Schreiben von Code verantwortlich und andere komplexe Konvertierungsarbeiten werden stillschweigend von der Entwicklungsplattform ausgeführt.

Nach meinem Verständnis kann der Prozess vom Quellcode bis zur ausführbaren Datei in die folgenden Phasen unterteilt werden:

1. Vom Quellcode bis zur Maschinensprache und der daraus resultierenden Maschinensprache nach bestimmten Regeln organisiert. Nennen wir es vorerst Datei A.

2. Verknüpfen Sie Datei A mit Datei B (z. B. Bibliotheksfunktion), die zum Ausführen von A erforderlich ist, um Datei A+ zu bilden.

3. Laden Sie Datei A+ in den Speicher und führen Sie die Datei aus

(Wenn Sie Nachschlagewerke oder andere Informationen lesen, sind möglicherweise mehr als diese Schritte erforderlich, aber der Einfachheit halber fasse ich sie in drei Schritten zusammen.)

Dies sind die wichtigsten Schritte zum Erstellen einer ausführbaren Datei. und keiner von ihnen darf fehlen. Jetzt sehen Sie, dass Sie von der Entwicklungsplattform „geblendet“ sind. Die folgenden Abschnitte werden den Nebel aufklären und Ihnen das wahre Gesicht Ihrer Entwicklungsplattform zeigen.

Objektdatei

Es gibt ein klassisches Sprichwort im Computerbereich:

„Jedes Problem in der Informatik kann durch eine weitere Ebene der Indirektion gelöst werden“

„Jedes Problem im Bereich der Informatik kann durch Hinzufügen einer Zwischenschicht gelöst werden“

Um beispielsweise von A nach B zu konvertieren, können Sie zuerst A in die Datei A+ konvertieren und dann die Datei A+ in uns Erforderliches Dokument B. (Tatsächlich wird diese Methode auch in Polyas „Wie man es liebt“ beschrieben. Beim Lösen von Problemen können Sie das Problem durch Hinzufügen einer Zwischenschicht vereinfachen.)

Also vom Quellcode zur ausführbaren Datei Der Prozess kann sein so verstanden. Das Gleiche gilt für den Übergang vom Quellcode zu ausführbaren Dateien, indem zur Lösung des Problems (ständig) dazwischen Zwischenschichten hinzugefügt werden.

Wie oben erwähnt, konvertieren Sie zuerst das Quellprogramm in die Zwischendatei A und dann die Zwischendatei in die von uns benötigte Zieldatei.

So gehen Sie bei der Bearbeitung von Dateien vor.

Tatsächlich lautet der oben erwähnte professionellere Begriff für Datei A: Zieldatei. Es ist kein ausführbares Programm und muss mit anderen Zieldateien verknüpft und geladen werden, bevor es ausgeführt werden kann. Bei einem Quellprogramm muss die Entwicklungsplattform zunächst das Quellprogramm in Maschinensprache übersetzen. Ein sehr wichtiger Teil davon ist die Zusammenstellung. Ich glaube, viele Leute wissen, dass es darum geht, Quellcode in Maschinensprache zu übersetzen (eigentlich eine Reihe von Binärcodes). Kompilierungskenntnisse sind sehr wichtig, stehen aber nicht im Mittelpunkt dieses Artikels. Wenn Sie interessiert sind, können Sie sie selbst googeln.

Zieldateiformat:

Nun schauen wir uns an, wie die oben erwähnte Zieldatei organisiert ist (d. h. die Speicherstruktur).

Ursprung:

Stellen Sie sich vor, wie Sie diese Binärdateien organisieren würden, wenn Sie derjenige wären, der sie entwirft? So wie die Gegenstände auf dem Schreibtisch klassifiziert und ordentlich platziert werden müssen, werden auch die übersetzten Binärcodes zur einfacheren Verwaltung in Kategorien gespeichert, indem diejenigen, die Code repräsentieren, und diejenigen, die Daten repräsentieren, zusammengefasst werden. Auf diese Weise wird der Binärcode zur Speicherung in verschiedene Blöcke unterteilt. Ein solcher Bereich wird als Segment bezeichnet.

Standards:

Wie viele Dinge in der Informatik, um die Kommunikation der Menschen, die Programmkompatibilität und andere Probleme zu erleichtern. Auch für diese binäre Speichermethode wurde ein Standard entwickelt, wodurch COFF (Common Object File Format) geboren wurde. Das Zieldateiformat unter aktuellen Mainstream-Betriebssystemen wie Windows und Linux ähnelt COFF und kann als Variante davon betrachtet werden.

a.out:

a.out ist der Standardname der Zieldatei. Mit anderen Worten: Wenn beim Kompilieren einer Datei die kompilierte Zieldatei nicht umbenannt wird, wird nach der Kompilierung eine Datei mit dem Namen a.out generiert.

Auf die konkreten Gründe, warum dieser Name hier verwendet wird, werde ich nicht näher eingehen. Wenn Sie interessiert sind, können Sie es selbst googeln.

Das folgende Bild kann Ihnen ein intuitiveres Verständnis der Zieldatei vermitteln:

Apropos Programmbetriebsmechanismus von Hello World


Das obige Bild ist ein typische Zieldatei Die Struktur und die tatsächliche Situation können unterschiedlich sein, sie werden jedoch alle auf dieser Basis abgeleitet.

ELF-Dateikopf: das erste Segment im Bild oben. Der Header ist der Header der Zieldatei, der einige grundlegende Informationen über die Zieldatei enthält. Zum Beispiel die Dateiversion, das Zielmaschinenmodell, die Programmeintragsadresse usw.

Textsegment: Die darin enthaltenen Daten sind hauptsächlich der Codeteil des Programms.

Datensegment: der Datenteil des Programms, z. B. Variablen.

Relocation-Segment:

Das Relocation-Segment umfasst Text-Relocation und Daten-Relocation, die Relocation-Informationen enthalten. Im Allgemeinen gibt es Situationen im Code, die auf externe Funktionen oder Variablen verweisen. Da es sich um eine Referenz handelt, sind diese Funktionen und Variablen in der Zieldatei nicht vorhanden. Bei der Nutzung ist die Angabe der tatsächlichen Adresse erforderlich (dieser Vorgang erfolgt bei der Verlinkung). Diese Umzugstabellen liefern die Informationen zum Auffinden dieser tatsächlichen Adressen. Nach dem Verständnis des oben Gesagten sind Textverschiebung und Datenverschiebung nicht schwer zu verstehen.

Symboltabelle: Die Symboltabelle enthält alle Symbolinformationen im Quellcode. Geben Sie alle Variablennamen, Funktionsnamen usw. an. Die Informationen zu jedem Symbol werden darin aufgezeichnet. Wenn der Code beispielsweise das Symbol „Student“ enthält, werden die entsprechenden Informationen zu diesem Symbol in die Symboltabelle aufgenommen. Einschließlich des Segments, in dem sich dieses Symbol befindet, seiner Attribute (Lese- und Schreibberechtigungen) und anderen zugehörigen Informationen.

Tatsächlich kann man sagen, dass sich die ursprüngliche Quelle der Symboltabelle in der lexikalischen Analysephase der Kompilierung befindet. Bei der lexikalischen Analyse werden jedes Symbol und seine Attribute im Code in der Symboltabelle aufgezeichnet.

String-Tabelle: Sie hat ähnliche Funktionen wie die Symboltabelle und speichert einige String-Informationen.

Eine weitere Sache ist, dass die Zieldateien alle im Binärformat gespeichert sind und es sich bei ihnen selbst um Binärdateien handelt.

Die Zieldatei wird in Wirklichkeit komplizierter sein als dieses Modell, aber die Idee ist dieselbe, das heißt, sie wird je nach Typ gespeichert, plus einige Abschnitte, die die Zieldateiinformationen und die darin benötigten Informationen beschreiben der Link.

a.out-Segmentierung

Hello World

Nichts zu beweisen

Einfacher Hallo-Welt-Quellcode:

Apropos Programmbetriebsmechanismus von Hello World


Damit Daten in das Datensegment eingefügt werden können, muss „int a=5“.

Wenn es sich um VC handelt, klicken Sie auf „Ausführen“, um die Ergebnisse anzuzeigen.

Um klar zu sehen, wie es intern verarbeitet wird, verwenden wir GCC zum Kompilieren.

Führen Sie

gcc hello.c aus

Wenn Sie sich unser Verzeichnis ansehen, finden Sie dort eine zusätzliche Zieldatei a.out.

Apropos Programmbetriebsmechanismus von Hello World


Was wir jetzt tun wollen, ist zu sehen, was in a.out enthalten ist. Möglicherweise gibt es Kinder, die sich daran erinnern, vim-Text verwendet zu haben Schau es dir an. Das dachte ich damals naiv. Aber was ist das für ein Ding, wie kann man es so leicht entlarven? Ja, vim funktioniert nicht. „Die meisten Probleme, auf die wir gestoßen sind, wurden von Vorgängern festgestellt und gelöst.“ Ja, es gibt ein sehr leistungsfähiges Tool namens objdump. Damit können wir verschiedene Details der Zieldatei gründlich verstehen. Natürlich gibt es auch eine sehr nützliche Datei namens readelf, die später vorgestellt wird.

Diese beiden Tools werden im Allgemeinen mit Linux geliefert, Sie können sie selbst googeln

Hinweis: Der Code hier wird hauptsächlich mit GCC unter Linux kompiliert und die Zieldateien werden mit Objdump und readelf angezeigt. Aber ich werde alle laufenden Ergebnisse ins Bild setzen. Wenn Sie also noch nie mit Linux in Berührung gekommen sind, ist es kein Problem, den folgenden Inhalt zu lesen. Ich verwende Ubuntu und es fühlt sich gut an~

Das Folgende ist die Organisationsstruktur von a.out: (Startadresse jedes Segments, Größe usw.)

Der Befehl zum Anzeigen des Ziels Die Datei ist objdump -h a.out

Apropos Programmbetriebsmechanismus von Hello World


ist das gleiche wie das oben beschriebene Format der Zieldatei es wird in Kategorien gespeichert. Die Zieldatei ist in 6 Abschnitte unterteilt.

Von links nach rechts ist die erste Spalte (IDX-Name) der Name des Segments, die zweite Spalte (Größe) ist die Größe, VMA ist die virtuelle Adresse, LMA ist die physische Adresse und Datei aus ist der Offset innerhalb der Datei. Das heißt, der Abstand dieses Absatzes relativ zu einer Referenz im Absatz (normalerweise dem Anfang des Absatzes). Das letzte Algn ist eine Beschreibung der Segmentattribute. Ignorieren Sie es vorerst.

„Text“-Segment: Code-Segment.

„Daten“-Segment: Dies ist das oben erwähnte Datensegment, das die Daten im Quellcode speichert, normalerweise initialisierte Daten.

„bss“-Segment: Es ist auch ein Datensegment, in dem nicht initialisierte Daten gespeichert werden. Da diesen Daten noch kein Speicherplatz zugewiesen wurde, werden sie separat gespeichert.

„rodata“-Segment: schreibgeschütztes Datensegment, die darin gespeicherten Daten sind schreibgeschützt.

„cmment“ speichert Compiler-Versionsinformationen.

Die verbleibenden zwei Absätze haben für unsere Diskussion keine praktische Bedeutung und werden nicht noch einmal eingeführt. Denken Sie nur daran, dass sie einige Verknüpfungs-, Kompilierungs- und Installationsinformationen enthalten.

Hinweis:

Das Zieldateiformat listet hier nur die Hauptteile der tatsächlichen Situation auf. Es gibt einige tatsächliche Situationen, die in der Tabelle nicht aufgeführt sind. Wenn Sie auch Linux verwenden, können Sie objdump -X verwenden, um detailliertere Segmentinhalte aufzulisten.

Gehen Sie tief in a.out ein

Der obige Teil beschreibt die typischen Segmente in der Zieldatei anhand von Beispielen, hauptsächlich die Segmentinformationen wie Größe und andere verwandte Attribute.

Was genau ist in diesen Segmenten enthalten? Was genau ist im Segment „Text“ gespeichert?

objdump -s a.out Sie können das Hexadezimalformat der Zieldatei über die Option -s anzeigen.

Sehen Sie sich die Ergebnisse wie folgt an:

Apropos Programmbetriebsmechanismus von Hello World


Wie in der Abbildung oben gezeigt, ist die hexadezimale Darstellung jedes Segments aufgeführtes Formular. Es ist zu erkennen, dass die Abbildung in zwei Spalten unterteilt ist. Die Spalte links ist die hexadezimale Darstellung und die Spalte rechts zeigt die entsprechenden Informationen an.

Offensichtlich gibt es „Hallo Welt“ im schreibgeschützten Datensegment „rodata“. . Seufz, es scheint, dass „Hallo“ im Programm falsch eingegeben wurde und am Ende ein zusätzliches „w“ hinzugefügt wurde. Es ist mühsam, Screenshots zu machen. Verzeihen Sie mir.

Sie können auch den ASCII-Wert von „hellow world“ überprüfen. Der entsprechende Hexadezimalwert ist der Inhalt darin.

"Kommentar" Der oben erwähnte Absatz enthält einige Informationen zur Compilerversion. Der Inhalt nach diesem Absatz lautet: GCC-Compiler, gefolgt von der Versionsnummer.

a.out-Disassemblierung

Der Kompilierungsprozess wandelt den Quelltext immer zuerst in Assemblerform um und übersetzt ihn dann in Maschinensprache. (Fügen Sie eine mittlere Ebene hinzu.) Nachdem Sie so viele a.out gesehen haben, müssen Sie die Assemblierungsform studieren.

objdump -d a.out kann die Assemblierungsform der Datei auflisten. Allerdings wird hier nur der Hauptteil aufgeführt, also der Hauptfunktionsteil. Tatsächlich gibt es zu Beginn der Ausführung der Hauptfunktion und nach der Ausführung der Hauptfunktion noch viel zu tun.

bedeutet, die Funktionsausführungsumgebung zu initialisieren und den von der Funktion belegten Speicherplatz freizugeben.

Apropos Programmbetriebsmechanismus von Hello World


Im Bild oben ist die linke Seite die hexadezimale Form des Codes und die linke Seite die Assemblerform. Kinder, die mit dem Zusammenbau vertraut sind, sollten das meiste davon verstehen können, deshalb werde ich hier nicht auf Details eingehen.

a.out-Header-Datei

Bei der Einführung des Zieldateiformats wurde das Konzept der Header-Datei erwähnt, die einige grundlegende Informationen der Zieldatei enthält. Zum Beispiel die Dateiversion, das Zielmaschinenmodell, die Programmeintragsadresse usw.

Das folgende Bild zeigt das Format des Dateikopfes:

Sie können readelf -h verwenden, um ihn anzuzeigen. (Was im Bild unten angezeigt wird, ist hello.o, eine Datei, die kompiliert, aber nicht durch die Quelldatei hello.c verknüpft ist. Dies ist größtenteils dasselbe wie das Anzeigen von a.out)

Apropos Programmbetriebsmechanismus von Hello World


Das Bild ist in zwei Spalten unterteilt. Die linke Spalte stellt die Attribute dar und die rechte Spalte stellt die Attributwerte dar. Die erste Reihe wird oft als magische Zahl bezeichnet. Was folgt, ist eine Reihe von Zahlen. Ich werde nicht näher auf ihre spezifische Bedeutung eingehen. Sie können sie selbst googeln.

Im Folgenden finden Sie einige Informationen zur Zieldatei. Da es nicht eng mit dem Thema zusammenhängt, das wir diskutieren möchten, werden wir es hier nicht diskutieren.

Der obige Inhalt verwendet spezifische Beispiele, um die interne Organisationsform der Zieldatei zu beschreiben. Wie das Programm ausgeführt wird, ist lediglich ein Zwischenprozess Zieldatei ist Wie man sie in eine ausführbare Datei umwandelt und wie die ausführbare Datei ausgeführt wird, wird in den folgenden Abschnitten besprochen.

Ein einfaches Verständnis von Links

Für Laien bedeutet ein Link „zu“. Kombinieren Sie mehrere ausführbare Dateien.

Wenn Programm A auf eine in Datei B definierte Funktion verweist, muss der Funktionsteil in B im Quellcode von A platziert werden, damit die Funktion in A normal ausgeführt werden kann, und dann A und B Der Prozess Das Zusammenführen in einer Datei ist das Verknüpfen.

Es gibt einen speziellen Prozess zum Verknüpfen von Programmen, einen sogenannten Linker. Er verarbeitet einige Eingabezieldateien und synthetisiert sie zu einer Ausgabedatei. Diese Zieldateien verfügen häufig über gegenseitige Daten- und Funktionsverweise.

Oben haben wir die Zerlegungsform von „Hallo Welt“ gesehen, bei der es sich um eine Datei handelt, die nicht verknüpft wurde. Dies bedeutet, dass beim Verweisen auf eine externe Funktion deren Adresse nicht bekannt ist:

Wie gezeigt im Bild unten:

Apropos Programmbetriebsmechanismus von Hello World


Im Bild oben ruft die cal-Anweisung die printf()-Funktion auf, weil die printf()-Funktion Diese Datei ist derzeit nicht vorhanden, daher kann ihre Adresse nicht ermittelt werden und ihre Adresse wird durch „ff ff ff“ im Hexadezimalformat dargestellt. Nach der Verknüpfung wird diese Adresse zur tatsächlichen Adresse der Funktion, da die Funktion nach der Verknüpfung in die Datei geladen wurde.

Klassifizierung von Links: Links können entsprechend der Reihenfolge, in der A-bezogene Daten oder Funktionen in einer Datei zusammengeführt werden, in statische Links und dynamische Links unterteilt werden.

Statischer Link:

Schließen Sie die Verknüpfungsarbeiten ab, bevor das Programm ausgeführt wird. Das heißt, die Datei kann erst ausgeführt werden, wenn die Verknüpfung abgeschlossen ist. Dies hat jedoch einen offensichtlichen Nachteil, beispielsweise Bibliotheksfunktionen. Wenn sowohl Datei A als auch Datei B eine bestimmte Bibliotheksfunktion verwenden müssen, verfügen die verknüpften Dateien nach Abschluss der Verknüpfung über diese Bibliotheksfunktion. Wenn A und B gleichzeitig ausgeführt werden, befinden sich zwei Kopien der Bibliotheksfunktion im Speicher, was zweifellos Speicherplatz verschwendet. Diese Verschwendung wird besonders deutlich, wenn der Maßstab zunimmt. Statische Links haben außerdem den Nachteil, dass sie schwierig zu aktualisieren sind. Um diese Probleme zu lösen, verwenden viele Programme heute dynamische Verknüpfungen.

Dynamischer Link: Im Gegensatz zum statischen Link wird der dynamische Link verknüpft, wenn das Programm ausgeführt wird. Dann wird das Programm geladen und ausgeführt. Wenn im obigen Beispiel sowohl A als auch B die Bibliotheksfunktion Fun() verwenden, muss sich bei der Ausführung von A und B nur eine Kopie von Fun() im Speicher befinden.

Es gibt viel Wissen über Links, das in Zukunft in einem eigenen Artikel besprochen wird. Ich werde hier nicht auf Details eingehen.

Eine einfache Erklärung des Ladens

Wir wissen, dass ein Programm in den Speicher geladen werden muss, damit es ausgeführt werden kann. In früheren Maschinen wurde das gesamte Programm in den physischen Speicher geladen. Heutzutage wird im Allgemeinen ein virtueller Speichermechanismus verwendet, dh jeder Prozess verfügt über einen vollständigen Adressraum, sodass der Eindruck entsteht, dass jeder Prozess diesen verwenden kann. Ein Speichermanager ordnet dann die virtuellen Adressen den tatsächlichen physischen Speicheradressen zu.

Gemäß der obigen Beschreibung kann die Adresse des Programms in virtuelle Adresse und reale Adresse unterteilt werden. Die virtuelle Adresse ist ihre Adresse in ihrem virtuellen Speicherbereich und die physische Adresse ist die tatsächliche Adresse, an die sie geladen wird.

Apropos Programmbetriebsmechanismus von Hello World


Vielleicht ist Ihnen beim Betrachten der obigen Segmente aufgefallen, dass, da die Datei nicht verknüpft oder geladen ist, jeweils die virtuelle Adresse und die physische Adresse vorhanden sind des Segments sind beide 0. Der Ladevorgang von

kann wie folgt verstanden werden: Weisen Sie zunächst jedem Teil des Programms virtuelle Adressen zu und erstellen Sie dann eine Zuordnung von der virtuellen zur physischen Adresse. Tatsächlich ist der Schlüsselteil der Zuordnungsprozess von der virtuellen Adresse zur physischen Adresse. Nach der Installation des Programms zeigt der Programmzähler-PC der CPU auf die Startposition des Codes in der Datei, und dann wird das Programm nacheinander ausgeführt.

Der Zweck des Schreibens dieses Artikels besteht darin, den Mechanismus des Programmbetriebs zu klären und herauszufinden, was sich hinter der Ausführung einer ausführbaren Datei verbirgt. Vom Quellcode bis zur ausführbaren Datei gibt es in der Regel viele Zwischenschritte, die jeweils eine Zwischendatei erzeugen. Es ist nur so, dass die aktuelle integrierte Entwicklungsumgebung diese Schritte ausgeblendet hat. Wir, die wir an die integrierte Entwicklungsumgebung gewöhnt sind, haben diese wichtigen technischen Insider nach und nach ignoriert. In diesem Artikel wird nur die Hauptlinie dieses Prozesses vorgestellt. Jedes Detail kann in einem Artikel besprochen werden.

Ich denke, nach dem Lesen dieses Artikels wird nicht jeder denken, dass „Hallo Welt“ nur ein einfaches kleines Experiment ist. Ich hoffe auch, dass durch diesen Artikel jeder versteht, was der Funktionsmechanismus des Programms ist und was es ist. Wie es funktioniert.

Verwandte Empfehlungen:

Der zugrunde liegende Betriebsmechanismus und die Prinzipien von PHP

Erkundung des Funktionsbetriebsmechanismus von PHP_PHP-Tutorial

Beispielcodeanalyse für den JavaScript-Ausführungsmechanismus

Das obige ist der detaillierte Inhalt vonApropos Programmbetriebsmechanismus von Hello World. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!