Was ist die Verwendung von _start() in C?

Lesezeit: 7 Minuten

Benutzeravatar von SimpleGuy
Einfacher Kerl

Ich habe von meinem Kollegen gelernt, dass man ein C-Programm schreiben und ausführen kann, ohne a zu schreiben main() Funktion. Es kann so gemacht werden:

my_main.c

/* Compile this with gcc -nostartfiles */

#include <stdlib.h>

void _start() {
  int ret = my_main();
  exit(ret); 
}

int my_main() {
  puts("This is a program without a main() function!");
  return 0; 
}

Kompilieren Sie es mit diesem Befehl:

gcc -o my_main my_main.c –nostartfiles

Führen Sie es mit diesem Befehl aus:

./my_main

Wann sollte man so etwas tun? Gibt es ein reales Szenario, in dem dies nützlich wäre?

  • Entfernt verwandt: stackoverflow.com/questions/2548486/compiling-without-libc

    – Mohit Jain

    17. April 2015 um 9:09 Uhr

  • Klassischer Artikel, der einige der inneren Abläufe beim Starten von Programmen demonstriert: Ein Wirbelwind-Tutorial zum Erstellen wirklich winziger ausführbarer ELF-Dateien für Linux. Dies ist eine gute Lektüre, die einige der Feinheiten von bespricht _start() und andere Sachen außerhalb main().

    Benutzer439793

    17. April 2015 um 14:44 Uhr

  • Die C-Sprache selbst sagt nichts darüber aus _startoder über jeden Einstiegspunkt außer main (mit der Ausnahme, dass der Name des Einstiegspunkts für freistehende (eingebettete) Implementierungen implementierungsdefiniert ist).

    – Keith Thompson

    1. Juni 2015 um 3:17 Uhr

  • Beachten Sie, dass dies _start ist unsicher und verletzt die ABI, wenn es anruft my_main; Sie sagen dem Compiler, dass es sich um eine normale Funktion handelt, aber tatsächlich wird sie mit bereits ausgerichtetem Stapelzeiger eingegeben (z. B. auf x86-64, RSP % 16 == 0), nicht mit RSP % 16 == 8 wie beim Eintritt in eine normale Funktion danach a call das drückt eine 8-Byte-Rückgabeadresse. Das kannst du mit beheben __attribute__((force_align_arg_pointer)) zum _start um GCC mitzuteilen, dass der Stack-Zeiger beim Eintritt in diese eine “Funktion” möglicherweise “falsch ausgerichtet” ist, wie in Get arg values ​​with inline asm without Glibc?

    – Peter Cordes

    10. Dezember 2020 um 0:38 Uhr


  • Auf einer modernen Linux-Distribution würde dies zu einem Absturz führen, wenn my_main verwendet scanf überhaupt oder printf (oder eine andere variadische Funktion) mit a floar oder double FP-arg. glibc scanf Segmentierungsfehler, wenn sie von einer Funktion aufgerufen werden, die RSP nicht ausrichtet

    – Peter Cordes

    10. Dezember 2020 um 0:40 Uhr

Das Symbol _start ist der Einstiegspunkt Ihres Programms. Das heißt, die Adresse dieses Symbols ist die Adresse, zu der beim Programmstart gesprungen wird. Normalerweise die Funktion mit dem Namen _start wird von einer Datei namens bereitgestellt crt0.o die den Startcode für die C-Laufzeitumgebung enthält. Es richtet einige Dinge ein und füllt das Argument-Array argvzählt, wie viele Argumente vorhanden sind, und ruft dann auf main. Nach main kehrt zurück, exit wird genannt.

Wenn ein Programm die C-Laufzeitumgebung nicht verwenden möchte, muss es dafür eigenen Code bereitstellen _start. Beispielsweise tut dies die Referenzimplementierung der Programmiersprache Go, weil sie ein nicht standardmäßiges Threading-Modell benötigt, das etwas Magie mit dem Stack erfordert. Es ist auch nützlich, Ihre eigenen zu liefern _start wenn Sie wirklich kleine Programme oder Programme schreiben wollen, die unkonventionelle Dinge tun.

  • Ein weiteres Beispiel ist der dynamische Linker/Loader von Linux, für den ein eigenes _start definiert ist.

    – PP

    17. April 2015 um 10:52 Uhr

  • @BlueMoon Aber das_start stammt aus der Objektdatei crt0.ozu.

    – fuz

    17. April 2015 um 10:56 Uhr

  • @ThomasMatthews Der Standard gibt nicht an _start; Tatsächlich wird nicht angegeben, was vorher passiert main überhaupt aufgerufen wird, es gibt nur an, welche Bedingungen wann erfüllt sein müssen main wird genannt. Es ist eher eine Konvention für den Einstiegspunkt zu sein _start die aus alten Zeiten stammt.

    – fuz

    17. April 2015 um 19:18 Uhr

  • “Die Referenzimplementierung der Go-Programmiersprache tut dies, weil sie ein nicht standardmäßiges Threading-Modell benötigen” crt0.o ist C-spezifisch (crt->C-Laufzeit). Es gibt keinen Grund zu erwarten, dass es für andere Sprachen verwendet wird. Und das Threading-Modell von Go ist vollständig standardkonform

    – Steve Cox

    17. April 2015 um 20:19 Uhr

  • @SteveCox Viele Programmiersprachen bauen auf der C-Laufzeit auf, da es einfacher ist, Sprachen auf diese Weise zu implementieren. Go verwendet nicht das normale Threading-Modell. Sie verwenden kleine, Heap-zugewiesene Stacks und ihren eigenen Scheduler. Dies ist sicherlich kein Standard-Threading-Modell.

    – fuz

    17. April 2015 um 20:34 Uhr

Benutzeravatar von MikeMB
MikeMB

Während main ist der Einstiegspunkt für Ihr Programm aus Programmierer-Perspektive, _start ist der übliche Einstiegspunkt aus Sicht des Betriebssystems (die erste Anweisung, die ausgeführt wird, nachdem Ihr Programm vom Betriebssystem gestartet wurde)

In einem typischen C- und insbesondere C++-Programm wurde viel Arbeit geleistet, bevor die Ausführung in main eintritt. Besonders Dinge wie die Initialisierung globaler Variablen. Hier finden Sie eine gute Erklärung für alles, was dazwischen vor sich geht _start() und main() und auch nachdem main wieder beendet wurde (siehe Kommentar unten).
Der notwendige Code dafür wird normalerweise von den Compiler-Autoren in einer Startdatei bereitgestellt, jedoch mit dem Flag –nostartfiles Sie sagen dem Compiler im Wesentlichen: “Geben Sie mir nicht die Standard-Startdatei, geben Sie mir von Anfang an die volle Kontrolle darüber, was passiert.”

Dies ist manchmal notwendig und wird häufig auf eingebetteten Systemen verwendet. ZB wenn Sie kein Betriebssystem haben und bestimmte Teile Ihres Speichersystems (zB Caches) vor der Initialisierung Ihrer globalen Objekte manuell aktivieren müssen.

  • Die globalen Variablen sind Teil des Datenabschnitts und werden daher während des Ladens des Programms eingerichtet (wenn sie konstant sind, sind sie Teil des Textabschnitts, gleiche Geschichte). Die _start-Funktion ist davon völlig unabhängig.

    – Cheiron

    17. April 2015 um 13:33 Uhr

  • @Cheiron: Entschuldigung, mein Fehler In c++ werden globale Variablen oft von einem Konstruktor initialisiert, der intern ausgeführt wird _start() (oder eigentlich eine andere von ihr aufgerufene Funktion) und in vielen Bare-Metal-Programmen kopiert man explizit zuerst alle globalen Daten vom Flash ins RAM, was auch in geschieht _start()aber bei dieser Frage ging es weder um c++ noch um Bare-Metal-Code.

    – MikeMB

    17. April 2015 um 15:04 Uhr

  • Beachten Sie, dass in einem Programm, das seine eigenen liefert _start, wird die C-Bibliothek nicht initialisiert, es sei denn, Sie unternehmen spezielle Schritte, um dies selbst zu tun – es kann durchaus unsicher sein, eine nicht asynchronsignalsichere Funktion aus einem solchen Programm zu verwenden. (Dafür gibt es keine offizielle Garantie irgendein Die Bibliotheksfunktion wird funktionieren, aber asynchronsignalsichere Funktionen können überhaupt nicht auf globale Daten verweisen, sodass sie sich um eine Fehlfunktion bemühen müssen.)

    – zol

    17. April 2015 um 17:59 Uhr


  • @FUZxxl Trotzdem stelle ich fest, dass async-signalsichere Funktionen funktionieren sind modifizieren dürfen errno (z.B read und write sind async-signalsicher und können gesetzt werden errno) und das könnte möglicherweise ein Problem sein, je nachdem, wann genau der pro-thread errno Standort zugeordnet ist.

    – zol

    17. April 2015 um 19:39 Uhr

  • @FUZxxl Ich weiß genau, dass glibc’s malloc ist nicht async-signal-safe, aber ich akzeptiere die Korrektur für den abstrakten Fall.

    – zol

    17. April 2015 um 19:42 Uhr

Hier ist ein guter Überblick darüber, was während des Programmstarts passiert Vor main. Das zeigt sich vor allem __start ist der eigentliche Einstiegspunkt zu Ihrem Programm aus Sicht des Betriebssystems.

Es ist die allererste Adresse, von der aus die Befehlszeiger beginnt in Ihrem Programm zu zählen.

Der Code dort ruft einige Routinen der C-Laufzeitbibliothek auf, nur um etwas Ordnung zu schaffen, und ruft dann Ihre auf mainund dann bringen Sie die Dinge herunter und rufen Sie an exit mit welchem ​​Exit-Code auch immer main ist zurückgekommen.


Ein Bild sagt mehr als tausend Worte:

C-Laufzeit-Startdiagramm


PS: Diese Antwort stammt aus einer anderen Frage, die SO hilfreicherweise als Duplikat dieser Frage geschlossen hat.

  • Quer gepostet, um das Ausgezeichnete zu bewahren Analyse und das schöne Bild.

    – ulidtko

    20. Januar 2020 um 9:14 Uhr

Wann sollte man so etwas tun?

Wenn Sie Ihren eigenen Startcode für Ihr Programm wünschen.

main ist nicht der erste Eintrag für ein C-Programm, _start ist der erste Eintrag hinter dem Vorhang.

Beispiel unter Linux:

_start: # _start is the entry point known to the linker
    xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
    mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
    lea 8(%rsp), %rsi         # take the address of argv from the stack
    lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
    xor %eax, %eax            # per ABI and compatibility with icc
    call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main

    mov %eax, %edi    # transfer the return of main to the first argument of _exit
    xor %eax, %eax    # per ABI and compatibility with icc
    call _exit        # terminate the program

Gibt es ein reales Szenario, in dem dies nützlich wäre?

Wenn Sie meinen, implementieren Sie unsere eigenen _start:

Ja, in den meisten kommerziellen eingebetteten Softwareprogrammen, mit denen ich gearbeitet habe, müssen wir unsere eigene implementieren _start in Bezug auf unsere spezifischen Speicher- und Leistungsanforderungen.

Wenn du meinst, lass die fallen main Funktion und ändern Sie es in etwas anderes:

Nein, ich sehe keinen Vorteil darin.

1424150cookie-checkWas ist die Verwendung von _start() in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy