Wie wird ein C-Programm gestartet?
Wie wird in C die Methode main() anfänglich aufgerufen?
Benutzer418627
Zan Luchs
Das Betriebssystem ruft die main()
Funktion. Letztlich.
Das Ausführbares und verlinkbares Format (ELF) die viele Unix-Betriebssysteme verwenden, definiert eine Einstiegspunktadresse und eine INIT-Adresse. Dort beginnt das Programm zu laufen, nachdem das Betriebssystem seine Arbeit beendet hat exec()
Anruf. Auf einem Linux-System ist dies der Fall _init
in dem .init
Sektion. Danach springt es zur Einstiegspunktadresse, die ist _start
in dem .text
Sektion.
Der C-Compiler verknüpft eine Standardbibliothek mit jeder Anwendung, die diese vom Betriebssystem definierten Initialisierungs- und Einstiegspunkte bereitstellt. Diese Bibliothek ruft dann an main()
.
Hier ist mein C-Quellcode für das Beispiel:
#include <stdio.h>
int main() {
puts("Hello world!");
return 0;
}
Aus objdump -d
:
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 callq *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 retq
Disassembly of section .text:
0000000000001060 <_start>:
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp
1073: 4c 8d 05 66 01 00 00 lea 0x166(%rip),%r8 # 11e0 <__libc_csu_fini>
107a: 48 8d 0d ef 00 00 00 lea 0xef(%rip),%rcx # 1170 <__libc_csu_init>
1081: 48 8d 3d c1 00 00 00 lea 0xc1(%rip),%rdi # 1149 <main>
1088: ff 15 52 2f 00 00 callq *0x2f52(%rip) # 3fe0 <[email protected]_2.2.5>
108e: f4 hlt
108f: 90 nop
0000000000001140 <frame_dummy>:
1140: f3 0f 1e fa endbr64
1144: e9 77 ff ff ff jmpq 10c0 <register_tm_clones>
Aus readelf -h
Sie können die übereinstimmende Einstiegspunktadresse sehen _start
:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1060
Start of program headers: 64 (bytes into file)
Start of section headers: 17416 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 36
Section header string table index: 35
Aus readelf -d
:
Dynamic section at offset 0x2dc8 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x1000
0x000000000000000d (FINI) 0x11e8
0x0000000000000019 (INIT_ARRAY) 0x3db8
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x3dc0
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x3a0
0x0000000000000005 (STRTAB) 0x470
0x0000000000000006 (SYMTAB) 0x3c8
0x000000000000000a (STRSZ) 130 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x3fb8
0x0000000000000002 (PLTRELSZ) 24 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x5e0
0x0000000000000007 (RELA) 0x520
0x0000000000000008 (RELASZ) 192 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x500
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4f2
0x000000006ffffff9 (RELACOUNT) 3
0x0000000000000000 (NULL) 0x0
Sie können sehen, dass INIT gleich der Adresse von ist _init
.
Es gibt auch eine ganze Reihe von Funktionszeigern in INIT_ARRAY. Sehen objdump -s -j .init_array c-test
:
c-test: file format elf64-x86-64
Contents of section .init_array:
3db8 40110000 00000000 @.......
Sie können sehen, dass die Adresse 0x3db8 mit INIT_ARRAY im ELF-Header identisch ist.
Die Adresse 0x1140 (erinnern Sie sich an das Little-Endian-Byte-Layout von 40110000) ist die Funktion frame_dummy
Sie können in der Demontage sehen. Was dann anruft register_tm_clones
und wer weiß was noch.
Der Code für die Initialisierung befindet sich in einer Reihe von Dateien mit den Namen crtbegin.o und crtend.o (und Varianten dieser Namen). Das __libc_start_main
Funktion ist in libc.so.6 definiert. Diese Bibliotheken sind Teil von GCC. Dieser Code erledigt verschiedene Dinge, die für ein C-Programm erforderlich sind, wie das Einrichten von stdin, stdout, globalen und statischen Variablen und anderen Dingen.
Der folgende Artikel beschreibt ziemlich gut, was es unter Linux tut (aus einer Antwort unten mit weniger Stimmen entnommen): http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html
Ich glaube, die Antwort eines anderen hat bereits beschrieben, was Windows tut.
-
es ruft nicht
_init
oder irgend ein anderer. es ruft die Einstiegspunktadresse auf. es kann überall sein.– Andrej
12. August 2010 um 16:44 Uhr
-
BENÖTIGT libstdc++.so.6? Dies ist nicht aus einem C-Programm!
– Jens
3. Dezember 2012 um 15:54 Uhr
-
@Jens: In diesem Fall macht es keinen Unterschied.
– Zan Luchs
3. Dezember 2012 um 17:51 Uhr
-
@ZanLynx Woher weißt du das? Wie kann ich sagen? Warum nicht einfach ein C-Programm anschauen? C und C++ sind sehr unterschiedliche Sprachen, und der Programmstart ist einer der Bereiche, in denen dies sichtbar ist (z. B. beim Aufrufen von Konstruktoren).
– Jens
4. Dezember 2012 um 9:19 Uhr
-
@Jens: Wie kann Sie Sag es, da du mir nicht vertraust? Verwenden Sie den objdump -d Disassembler, um sich den Maschinencode anzusehen, und verwenden Sie readelf -d, um das ELF-Zeug anzuzeigen. Oder schnappen Sie sich einen Hex-Editor und eine Kopie der ELF-Spezifikation.
– Zan Luchs
5. Dezember 2012 um 7:24 Uhr
Andrej
Schließlich ist es Betriebssystem. Normalerweise gibt es zwischen realem Einstiegspunkt und Hauptfunktion ein Medium, das eingefügt wird Compiler Linker.
Einige Details (in Bezug auf Windows): Es gibt einen Header in der PE-Datei mit dem Namen IMAGE_OPTIONAL_HEADER
die das Feld hat AddressOfEntryPoint
die wiederum die Adresse des ersten Codebytes in der auszuführenden Datei ist.
-
Es wird nicht vom Compiler eingefügt, sondern vom Linker, normalerweise durch Verlinken auf etwas wie
crt.a(crt.o)
odercrt.lib(crt.obj)
was normalerweise Teil von so etwas istlibc.a
oderc.lib
.– Christian Hüjer
11. August 2015 um 16:11 Uhr
-
@ChristianHujer du hast recht, mit Compiler meinte ich gut die Toolchain.
– Andrej
11. August 2015 um 16:15 Uhr
-
Sie sicher Linkerich denke mal was Lader tut dann?
– Wurzelreisender
11. September 2017 um 11:23 Uhr
-
@roottraveller Linker-Links. Die ausführbare Datei wird möglicherweise sogar nie geladen.
– Andrej
11. September 2017 um 14:23 Uhr
-
Ich weiß nicht, warum dies abgelehnt wurde, der Link enthält gute Informationen
– zol
12. August 2010 um 18:04 Uhr
-
@Zack Im Allgemeinen ist es verpönt, hier einen Link ohne irgendeine Zusammenfassung zu posten
– Michael Mrozek
13. August 2010 um 15:10 Uhr
Das Betriebssystem ruft main auf. Es wird eine Adresse in der verschiebbaren ausführbaren Datei geben, die auf den Speicherort von main zeigt (weitere Informationen finden Sie in der Unix-ABI).
Aberwer ruft das Betriebssystem?
Die zentrale Verarbeitungseinheit beginnt bei dem “RESET”-Signal (das auch beim Einschalten geltend gemacht wird) damit, in irgendeinem ROM an einer gegebenen Adresse (z. B. 0xffff) nach seinen Anweisungen zu suchen.
Normalerweise gibt es eine Art Sprungbefehl zum BIOS, der die Speicherchips konfiguriert, die grundlegenden Festplattentreiber lädt usw. usw. Dann wird der Boot-Sektor der Festplatte gelesen und die nächste Bootloader wird gestartet, der die Datei lädt, die die grundlegenden Informationen enthält, wie man beispielsweise eine NTFS-Partition liest und wie man die Kernel-Datei selbst liest. Die Kernel-Umgebung wird eingerichtet, der Kernel geladen und dann – und dann! – Der Kernel wird zur Ausführung angesprungen.
Nachdem all diese harte Arbeit erledigt ist, kann der Kernel dann damit fortfahren, unsere Software zu laden.
Das Betriebssystem ruft eine Funktion auf, die in der C-Laufzeit (CRT) enthalten und mit Ihrer ausführbaren Datei verknüpft ist. Nennen Sie dies “CRT-Hauptleitung”.
CRT main macht ein paar Dinge, von denen die beiden wichtigsten, zumindest in C++, darin bestehen, ein Array globaler C++-Klassen zu durchlaufen und ihre Konstruktoren aufzurufen und Ihre main()-Funktion aufzurufen und ihren Rückgabewert an die Shell zu übergeben .
Der CRT-Hauptbildschirm von Visual C++ macht noch ein paar Dinge, wenn der Speicher reicht. Es konfiguriert den Speicherzuordner, was wichtig ist, wenn die Debug-CRT verwendet wird, um Speicherlecks oder fehlerhafte Zugriffe zu finden. Es ruft auch main innerhalb von a auf strukturierte Ausnahme Handler, der fehlerhaften Speicherzugriff und andere Abstürze abfängt und anzeigt.
-
Auch wenn Sie von C++ und nicht von C sprechen, ist Ihr Beitrag aufschlussreich!
– Gab是好人
9. Dezember 2016 um 18:25 Uhr
Brian
Beachten Sie, dass Sie zusätzlich zu den bereits geposteten Antworten auch anrufen können main
dich selbst. Im Allgemeinen ist dies eine schlechte Idee, die für verschleierten Code reserviert ist.
-
Auch wenn Sie von C++ und nicht von C sprechen, ist Ihr Beitrag aufschlussreich!
– Gab是好人
9. Dezember 2016 um 18:25 Uhr
Nandan Bharadwaj
Die wahrscheinlich besten Informationen zu Ihrer Frage finden Sie unter dem unten genannten Link
http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.htmldas Beste, was mir bis jetzt untergekommen ist.
-
Das ist großartig! Danke vielmals!
– Gab是好人
9. Dezember 2016 um 18:26 Uhr
Sie machen! Du rufst die main()! Ja, das ist richtig! Ja, das tun Sie!
– rekursiv
12. August 2010 um 16:41 Uhr
Wer beobachtet die Wächter?
– Kaskabel
12. August 2010 um 16:41 Uhr
Das Subjekt ist eine Aussage, aber der Körper ist eine Frage.
– Brian R. Bondy
12. August 2010 um 16:43 Uhr
Warum, Justin, warum? Es war Poesie!
– jball
12. August 2010 um 17:21 Uhr
Wow, die Art und Weise, wie sich diese Frage entwickelt, erinnert mich an “Wie bewege ich die Schildkröte in LOGO?”
– Brian
12. August 2010 um 17:53 Uhr