webentwicklung-frage-antwort-db.com.de

Dieser verschleierte C-Code behauptet, ohne main () ausgeführt zu werden, aber was macht er wirklich?

#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 main auf? Wie?

85
Rajeev Singh

Die Programmiersprache 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 Programmstartfunktion definiert werden, während in gehosteten Umgebung sollte es main sein. Kein Programm in C kann ohne Programmstartfunktion in den definierten Umgebungen ausgeführt werden.

In Ihrem Fall wird main von den Präprozessordefinitionen ausgeblendet. begin() wird zu decode(a,n,i,m,a,t,e) erweitert, was weiter zu main erweitert wird.

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. Die Ersatzliste für dieses Makro lautet m##s##u##t. m, s, u Und t sind 4th, 1st, 3rd und 2nd Parameter, der in der Ersetzungsliste verwendet wird.

s, t, u, m, p, e, d
1  2  3  4  5  6  7

Rest nützt nichts ( nur um zu verschleiern ). Das an decode übergebene Argument ist " a, n, i, m, a, t, e ", also werden die Bezeichner m, s, u und t durch Argumente m, a, i bzw. n ersetzt.

 m --> m  
 s --> a 
 u --> i 
 t --> n
193
haccks

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 generiert.

72
jdarthenay

Das betreffende Programm ruft main() aufgrund der Makroerweiterung auf , aber Ihre Annahme ist fehlerhaft - es nicht muss main() überhaupt aufrufen!

Genau genommen können Sie ein C-Programm haben und es kompilieren, ohne ein main -Symbol zu haben. main ist etwas, das der c library erwartet einen Sprung nach, nachdem es seine eigene Initialisierung abgeschlossen hat. Normalerweise springen Sie vom libc-Symbol namens main nach _start. Es ist immer möglich, ein sehr gültiges Programm zu haben, das einfach 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 */
}

Kompiliere das obige mit gcc -nostdlib without_main.c, und sehe es drucken Hello World! auf dem Bildschirm nur durch Ausgeben von Systemaufrufen (Interrupts) in der Inline-Assembly.

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, ohne dass das Symbol main einer C-Funktion entspricht. Zum Beispiel können Sie das Folgende als ein sehr gültiges C-Programm haben, das den Compiler nur zum Wimmern bringt, wenn Sie die Warnings-Ebene hochstufen.

/* 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 entsprechen, die zum Drucken von Hello World auf dem Bildschirm erforderlich sind. Eine genauere Beschreibung der Funktionsweise dieses spezifischen Programms finden Sie in diesem Blog-Beitrag , in dem ich es auch zuerst gelesen habe.

Ich möchte noch eine letzte Bemerkung zu diesen Programmen machen. Ich weiß nicht, ob sie sich als gültige C-Programme gemäß der C-Sprachspezifikation registrieren, aber das Kompilieren und Ausführen dieser Programme ist durchaus möglich, selbst wenn sie die Spezifikation selbst verletzen.

37
NlightNFotis

Jemand versucht, sich wie ein Magier zu verhalten. Er glaubt, er kann uns austricksen. Aber wir alle wissen, dass die Programmausführung mit main() beginnt.

Die int begin() wird durch decode(a,n,i,m,a,t,e) durch einen Durchgang der Präprozessorstufe ersetzt. Dann wird decode(a,n,i,m,a,t,e) durch m ## a ## i ## n ersetzt. Wie bei der Positionszuordnung eines Makroaufrufs hat s den Wert a. Ebenso wird u durch 'i' und t durch 'n' ersetzt. Und so wird m##s##u##t Zu main

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 das "##" und die beiden ursprünglichen Token in der Makroerweiterung ersetzt.

Wenn Sie mir nicht glauben, können Sie Ihren Code mit dem Flag -E Kompilieren. Der Kompilierungsprozess wird nach der Vorverarbeitung beendet und Sie können das Ergebnis des Einfügens von Token sehen.

gcc -E FILENAME.c
30
abhiarora

decode(a,b,c,d,[...]) mischt die ersten vier Argumente und fügt sie zusammen, um einen neuen Bezeichner in der Reihenfolge dacb zu erhalten. (Die restlichen drei Argumente werden ignoriert.) Beispielsweise gibt decode(a,n,i,m,[...]) den Bezeichner main an. Beachten Sie, dass das Makro begin so definiert ist.

Daher wird das Makro begin einfach als main definiert.

11
Frxstrem

In Ihrem Beispiel ist die Funktion main() tatsächlich vorhanden, da begin ein Makro ist, das der Compiler durch das Makro decode ersetzt, das wiederum durch den Ausdruck m ## s # ersetzt wird. # u ## t. 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() zu haben, 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 eines seiner Tools ab.

In Windows verwenden Sie nicht immer main(), sondern eher WinMain oder wWinMain , obwohl Sie können main(), auch mit der Microsoft-Toolchain . Unter Linux kann man _start Verwenden.

Es liegt am Linker als Betriebssystem-Tool, den Einstiegspunkt und nicht die Sprache selbst festzulegen. Sie können sogar setzen Sie unseren eigenen Einstiegspunkt, und Sie können eine Bibliothek erstellen, die auch ausführbar ist !

2
Ho1