#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Ruft dies indirekt auf main? Wie?
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
Ruft dies indirekt auf main? Wie?
Antworten:
Die Sprache C definiert die Ausführungsumgebung in zwei Kategorien: freistehend und gehostet . In beiden Ausführungsumgebungen wird von der Umgebung eine Funktion zum Programmstart aufgerufen.
In einer freistehenden Umgebung kann die Startfunktion eines Programms definiert werden, während dies in einer gehosteten Umgebung der Fall sein sollte main. Kein Programm in C kann ohne Programmstartfunktion in den definierten Umgebungen ausgeführt werden.
In Ihrem Fall mainwird durch die Präprozessordefinitionen ausgeblendet. begin()wird erweitert, auf decode(a,n,i,m,a,t,e)die weiter erweitert wird main.
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main()
decode(s,t,u,m,p,e,d)ist ein parametrisiertes Makro mit 7 Parametern. Ersatzliste für dieses Makro ist m##s##u##t. m, s, uund tgibt 4 th , 1 st , 3 rd und 2 nd Parameter in der Ersatzliste verwendet.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
Rest nützt nichts ( nur um zu verschleiern ). Argument übergeben decodeist " a , n , i , m , a, t, e" , so werden die Kennungen m, s, uund tersetzt werden mit Argumenten m, a, iund n, respectively.
m --> m
s --> a
u --> i
t --> n
_start(). Oder noch niedriger kann ich versuchen, den Start meines Programms einfach an der Adresse auszurichten, auf die die IP nach dem Start eingestellt ist. main()ist C Standard Bibliothek . C selbst unterwirft dies nicht.
decode(a,n,i,m,a,t,e)es wird m##a##i##n? Ersetzt es Zeichen? Können Sie einen Link zur Dokumentation der decodeFunktion bereitstellen ? Vielen Dank.
beginist so definiert, dass es durch decode(a,n,i,m,a,t,e)das zuvor definierte ersetzt wird. Diese Funktion nimmt die Argumente s,t,u,m,p,e,dund verkettet sie in dieser Form m##s##u##t( ##bedeutet verketten). Das heißt, es ignoriert die Werte von p, e und d. Wie Sie "Call" decodemit s = a, t = n, u = i, m = m ersetzt er effektiv beginmit main.
Versuchen Sie es mit gcc -E source.c, die Ausgabe endet mit:
int main()
{
printf("Ha HA see how it is?? ");
}
Eine main()Funktion wird also tatsächlich vom Präprozessor erzeugt.
Das betreffende Programm wirdmain() aufgrund einer Makroerweiterung aufgerufen , aber Ihre Annahme ist fehlerhaft - es muss überhaupt nicht aufgerufen werden main()!
Genau genommen können Sie ein C-Programm haben und es kompilieren können, ohne ein mainSymbol zu haben. mainist etwas, in das der c libraryerwartet, zu springen, nachdem er seine eigene Initialisierung abgeschlossen hat. Normalerweise springt man mainvom libc-Symbol, das als bekannt ist _start. Es ist immer möglich, ein sehr gültiges Programm zu haben, das einfach die Assembly ausführt, ohne ein Hauptprogramm zu haben. Schau dir das an:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
Kompilieren Sie das Obige mit gcc -nostdlib without_main.cund sehen Sie, wie es Hello World!auf dem Bildschirm gedruckt wird, indem Sie Systemaufrufe (Interrupts) in der Inline-Assembly ausgeben.
Weitere Informationen zu diesem speziellen Problem finden Sie im ksplice-Blog
Ein weiteres interessantes Problem ist, dass Sie auch ein Programm haben können, das kompiliert wird, ohne dass das mainSymbol einer C-Funktion entspricht. Zum Beispiel können Sie Folgendes als sehr gültiges C-Programm verwenden, das den Compiler nur dann zum Jammern bringt, wenn Sie die Warnstufe erhöhen.
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
Die Werte im Array sind Bytes, die den Anweisungen zum Drucken von Hello World auf dem Bildschirm entsprechen. Um eine detailliertere Darstellung der Funktionsweise dieses speziellen Programms zu erhalten, werfen Sie einen Blick auf diesen Blog-Beitrag , in dem ich ihn auch zuerst gelesen habe.
Ich möchte noch einen letzten Hinweis zu diesen Programmen geben. Ich weiß nicht, ob sie sich gemäß der C-Sprachspezifikation als gültige C-Programme registrieren, aber das Kompilieren und Ausführen dieser Programme ist sicherlich sehr gut möglich, selbst wenn sie gegen die Spezifikation selbst verstoßen.
_startTeils eines definierten Standards oder ist das nur implementierungsspezifisch? Sicherlich ist Ihr "main als Array" architekturspezifisch. Wichtig ist auch, dass es nicht unangemessen ist, wenn Ihr Trick "main as a array" zur Laufzeit aufgrund von Sicherheitsbeschränkungen fehlschlägt (obwohl dies wahrscheinlicher wäre, wenn Sie das constQualifikationsmerkmal nicht verwenden würden und viele Systeme dies dennoch zulassen würden).
_startist nicht im ELF-Standard enthalten, obwohl der AMD64 psABI einen Verweis auf _startbei 3.4 Process Initialization enthält . Offiziell kennt ELF nur die Adresse e_entryim ELF-Header, _startist nur ein Name, den die Implementierung gewählt hat.
constkeine Rolle - der Symbolname in dieser ausführbaren Binärdatei lautet main. Nicht mehr und nicht weniger. constist ein C-Konstrukt, das zur Ausführungszeit nichts bedeutet.
Jemand versucht, sich wie ein Magier zu verhalten. Er glaubt, er kann uns austricksen. Aber wir alle wissen, dass die Ausführung des c-Programms mit beginnt main().
Das int begin()wird decode(a,n,i,m,a,t,e)durch einen Durchgang der Präprozessorstufe ersetzt. Andererseits decode(a,n,i,m,a,t,e)wird durch m ## a ## i ## n ersetzt. Wie durch die Positionszuordnung des Makroaufrufs hat der sWille einen Zeichenwert a. Ebenso uwird durch 'i' und tdurch 'n' ersetzt. Und so m##s##u##twird es werdenmain
In Bezug auf das ##Symbol in der Makroerweiterung ist es der Vorverarbeitungsoperator und führt das Einfügen von Token durch. Wenn ein Makro erweitert wird, werden die beiden Token auf beiden Seiten jedes '##'-Operators zu einem einzigen Token kombiniert, das dann die' ## 'und die beiden ursprünglichen Token in der Makroerweiterung ersetzt.
Wenn Sie mir nicht glauben, können Sie Ihren Code mit -Eflag kompilieren . Der Kompilierungsprozess wird nach der Vorverarbeitung gestoppt und Sie können das Ergebnis des Einfügens von Token sehen.
gcc -E FILENAME.c
decode(a,b,c,d,[...])mischt die ersten vier Argumente und verbindet sie, um eine neue Kennung in der Reihenfolge zu erhalten dacb. (Die verbleibenden drei Argumente werden ignoriert.) Gibt beispielsweise decode(a,n,i,m,[...])den Bezeichner an main. Beachten Sie, dass das beginMakro so definiert ist.
Daher wird das beginMakro einfach definiert als main.
In Ihrem Beispiel ist die main()Funktion tatsächlich vorhanden, da begines sich um ein Makro handelt, das der Compiler durch ein Makro ersetzt, decodedas wiederum durch den Ausdruck m ## s ## u ## t ersetzt wird. Mit der Makro-Erweiterung ##erreichen Sie das Wort mainvon decode. Dies ist eine Spur:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
Es ist nur ein Trick main(), aber die Verwendung des Namens main()für die Eingabefunktion des Programms ist in der Programmiersprache C nicht erforderlich. Dies hängt von Ihren Betriebssystemen und dem Linker als einem seiner Tools ab.
Unter Windows verwenden Sie nicht immer main(), sondern eher WinMainoderwWinMain , obwohl Sie verwenden können main(), auch mit Microsofts Toolchain . Unter Linux kann man verwenden _start.
Es liegt am Linker als Betriebssystem-Tool, den Einstiegspunkt und nicht die Sprache selbst festzulegen. Sie können sogar unseren eigenen Einstiegspunkt festlegen und eine Bibliothek erstellen, die auch ausführbar ist !
main()Funktion an die Programmiersprache C bindet , die nicht korrekt ist.