#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 main
wird 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, u
und t
gibt 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 decode
ist " a , n , i , m , a, t, e" , so werden die Kennungen m, s, u
und t
ersetzt werden mit Argumenten m, a, i
und 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 decode
Funktion bereitstellen ? Vielen Dank.
begin
ist 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,d
und 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" decode
mit s = a, t = n, u = i, m = m ersetzt er effektiv begin
mit 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 main
Symbol zu haben. main
ist etwas, in das der c library
erwartet, zu springen, nachdem er seine eigene Initialisierung abgeschlossen hat. Normalerweise springt man main
vom 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.c
und 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 main
Symbol 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.
_start
Teils 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 const
Qualifikationsmerkmal nicht verwenden würden und viele Systeme dies dennoch zulassen würden).
_start
ist nicht im ELF-Standard enthalten, obwohl der AMD64 psABI einen Verweis auf _start
bei 3.4 Process Initialization enthält . Offiziell kennt ELF nur die Adresse e_entry
im ELF-Header, _start
ist nur ein Name, den die Implementierung gewählt hat.
const
keine Rolle - der Symbolname in dieser ausführbaren Binärdatei lautet main
. Nicht mehr und nicht weniger. const
ist 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 s
Wille einen Zeichenwert a
. Ebenso u
wird durch 'i' und t
durch 'n' ersetzt. Und so m##s##u##t
wird 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 -E
flag 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 begin
Makro so definiert ist.
Daher wird das begin
Makro einfach definiert als main
.
In Ihrem Beispiel ist die main()
Funktion tatsächlich vorhanden, da begin
es sich um ein Makro handelt, das der Compiler durch ein Makro ersetzt, decode
das wiederum durch den Ausdruck m ## s ## u ## t ersetzt wird. Mit der Makro-Erweiterung ##
erreichen Sie das Wort main
von 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 WinMain
oderwWinMain
, 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.