- Die Antwort auf Shakespear's "Two be or not two be" ist minus eins, ganz klar:
#include <stdio.h>
int main()
{
printf( "%25d\n", 0x2b | ~0x2b );
return 0;
} - -- SvOlli
Kristian Köhntopp erklärt in "
Dynamisch geladener Code" den Aufbau von Programmen in verschiedenen Variationen, wie monolitischen Code, Code in mehreren Modulen, mit statischen Bibliotheken, dynamischen Bibliotheken und das Zuladen von Code mithilfe der dlopen() Funktion. Gerade für den interessierten Nicht-Programmierer ein sehr lehrreicher Artikel.
Ich möchte den Bogen noch etwas weiter spannen, und eine Möglichkeit vorstellen C Sourcecode während der Laufzeit des eigentlichen Programmes zu übersetzen und integrieren. Klingt kaum möglich, ist aber so.
Mir ist vor einiger Zeit mal der
Tiny C Compiler (TCC) untergekommen. Dieser verspricht in wenigen hundert Kilobyte eine komplette Umgebung aus Compiler, Assembler und Linker. Entstanden ist das ganze aus einem Betrag von Fabrice Bellard für den
International Obfuscated C Code Contest (IOCCC). Dieser Wettbewerb allein wäre eigentlich schon einen Blogeintrag wert. Dort gilt es ein beliebiges, irgendwie pfiffiges Programm zu schreiben, welches seine Funktion bei der Analyse des Quellcodes nicht so ohne weiteres offenbart. Eigentlich der Albtraum eines jeden Programmierers, der fremden Code übernehmen muss. Hier ist es aber eher schon eine Kunstform. Der besagte
Beitrag von Fabrice Bellard war ein Compiler, der ein Subset der Programmiersprache C in i386 Code übersetzen und ausführen konnte. Sein Ziel war es, den kleinsten C Compiler zu schreiben, der sich selbst übersetzen konnte.
Daraus entstanden ist der Tiny C Compiler, welcher Code ungefähr 10 Mal so schnell übersetzt, wie der C Compiler der Gnu Compiler Collection (GCC). Der erzeugte Code selbst ist jedoch nicht so gut optimiert (soll heissen: schnell) wie der vom GCC. Dafür ermöglicht der TCC einem ein C Programm wie ein Shell-Script laufen zu lassen:
#!/usr/local/bin/tcc -run
#include <stdio.h>
int main( int argc, char **argv)
{
printf( "Hello, World!\n" );
return 0;
}
Dies könnte sich als sehr interessant für z.B. Rettungssysteme erweisen. Der Code ist kompakt, da der Objektcode meist grösser ist als der Sourcecode, und kann vor dem Start "noch mal eben schnell" angepasst werden. Der Compiler braucht inclusive einiger "Minimal-Headern" ca. 200 kB auf dem Zielsystem. Das Linken von statischen und dynamischen Libraries funktioniert auch wie man es von den "Grossen" erwartet.
Damit aber noch nicht genug. Im Rahmen des Compilieren des Compilers "fällt" quasi noch eine (statische) Bibliothek ab, welche es einem Ermöglicht, die Funktionen des Compilers aus einem eigenen Programm heraus aufzurufen. Als Beispiel für die Mächtigkeit dieser Bibliothek möchte ich hier das Programm libtcc_test.c anführen (ein klein bisschen verkürzt):
#include <stdlib.h>
#include <stdio.h>
#include "libtcc.h"
/\* this function is called by the generated code \*/
int add(int a, int b)
{
return a + b;
}
char my_program[] =
"int fib(int n)\n"
"{\n"
" if (n <= 2)\n"
" return 1;\n"
" else\n"
" return fib(n-1) + fib(n-2);\n"
"}\n"
"\n"
"int foo(int n)\n"
"{\n"
" printf(\"Hello World!\\n\");\n"
" printf(\"fib(%25d) = %25d\\n\", n, fib(n));\n"
" printf(\"add(%25d, %25d) = %25d\\n\", n, 2 \* n, add(n, 2 \* n));\n"
" return 0;\n"
"}\n";
int main(int argc, char \*\*argv)
{
TCCState \*s;
int (\*func)(int);
unsigned long val;
s = tcc_new();
if (!s) {
fprintf(stderr, "Could not create tcc state\n");
exit(1);
}
tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
tcc_compile_string(s, my_program);
/\* as a test, we add a symbol that the compiled program can be
linked with. \*/
tcc_add_symbol(s, "add", (unsigned long)&add);
tcc_relocate(s);
tcc_get_symbol(s, &val, "foo");
func = (void \*)val;
func(32);
tcc_delete(s);
return 0;
}
Wenn man sich zurücklehnt und den Code mal auf sich wirken lässt, wird einem schnell bewusst, was für süße Schweinereien man damit so alles anstellen kann.
Alternativ kann man auch Programm welches main() beinhaltet durch "int tcc_run(TCCState \*s, int argc, char \*\*argv);" in einer Art "Sandbox" laufen lassen. Nur leider bleibt ein Crash immer noch in Crash.
Verfügbar ist das Ganze als Quelltext für i386-Linux (mit Einschränkungen auf für FreeBSD), Windows wird via MinGW (oder auch - fast, es fehlt "ar" - selfhosted) unterstützt. Ausserdem ist das generieren von Code für ARM und für den DSP TMS320C67xx möglich. Die Frage, die mich im Moment da am brennensten interessiert, ist ob auch ARM-Linux selfhosted unterstützt wird. Sobald mein ARM-Linux Target angekommen ist, gibt's an dieser Stelle mehr.
Abschliessend empfehle ich noch einen Blick auf
TCCBOOT zu werfen. Dort baut der TCC in einem Proof-Of-Concept während des Hochfahrens einen Linuxkernel zusammen. Schon schick.
Kommentare