With the exception of graphics, everything else on Linux can easily be done via kernel system calls. For those working in a custom language, using GL adds the complexity nightmare of dynamic linking. However there is a way to reduce this complexity to the following (which enables a simpler ELF),
(1.) Link to libdl.so only.
(2.) Use only one symbol: dlsym().
Using dlsym() one can get the address for dlopen(), which together enables manually opening libraries. Unfortunately some GL libraries require that the caller have the full libc treatment (everything that happens between _start() and main()). Implementing all that crap in a custom language is a great way to gain the full realization that the Linux people lost track of the K.I.S.S. principle a long time ago. Instead one can just dlopen() libc.so itself and manually pass through the complexity filter via __libc_start_main(). Here is a proof-of-concept in C for x86-64 which lacks a bunch of important stuff, but works enough to get into main() and then print "ok" via a Linux syscall.
static inline long Linux1(long num, long arg1) { long ret; asm volatile ("syscall" : "=a" (ret) : "a" (num), "D" (arg1) : "cc", "memory", "%rcx", "%rdx", "%rsi", "%r8", "%r9", "%r10", "%r11"); return ret; }
static inline long Linux3(long num, long arg1, long arg2, long arg3) { long ret; asm volatile ("syscall" : "=a" (ret) : "a" (num), "D" (arg1), "S" (arg2), "d" (arg3) : "cc", "memory", "%rcx", "%r8", "%r9", "%r10", "%r11"); return ret; }
void* dlsym(void*, const char*);
typedef void* (*dlopenF)(const char*, int);
#define RTLD_NOW 2
typedef int (*__libc_start_mainF)(int (*)(int, char**, char**), int, char**, void (*)(void), void(*)(void), void (*)(void), void (*));
static void None(void) { }
static int Main(int a, char** b, char** c) { char msg[]="ok\n"; Linux3(1, 1, (long)msg, 3); Linux1(231, 0); }
void _start(void) {
dlopenF dlopen;
dlopen = (dlopenF)dlsym(0, "dlopen");
void* libc = dlopen("libc.so", RTLD_NOW);
__libc_start_mainF __libc_start_main;
__libc_start_main = (__libc_start_mainF)dlsym(libc, "__libc_start_main");
__libc_start_main(Main, 0, 0, None, None, None, 0); }
This can be built with,
gcc -std=gnu99 src.c -ldl -Os -fno-unwind-tables -fno-exceptions -nostdlib
Even given the unnecessary garbage GNU ld dumps in the ELF, this binary will be under 3K, which almost half the size of compiling "void main(void) { }" using the standard libs. Going to assembly or some other custom language and manually building the ELF can make this much smaller.
(1.) Link to libdl.so only.
(2.) Use only one symbol: dlsym().
Using dlsym() one can get the address for dlopen(), which together enables manually opening libraries. Unfortunately some GL libraries require that the caller have the full libc treatment (everything that happens between _start() and main()). Implementing all that crap in a custom language is a great way to gain the full realization that the Linux people lost track of the K.I.S.S. principle a long time ago. Instead one can just dlopen() libc.so itself and manually pass through the complexity filter via __libc_start_main(). Here is a proof-of-concept in C for x86-64 which lacks a bunch of important stuff, but works enough to get into main() and then print "ok" via a Linux syscall.
static inline long Linux1(long num, long arg1) { long ret; asm volatile ("syscall" : "=a" (ret) : "a" (num), "D" (arg1) : "cc", "memory", "%rcx", "%rdx", "%rsi", "%r8", "%r9", "%r10", "%r11"); return ret; }
static inline long Linux3(long num, long arg1, long arg2, long arg3) { long ret; asm volatile ("syscall" : "=a" (ret) : "a" (num), "D" (arg1), "S" (arg2), "d" (arg3) : "cc", "memory", "%rcx", "%r8", "%r9", "%r10", "%r11"); return ret; }
void* dlsym(void*, const char*);
typedef void* (*dlopenF)(const char*, int);
#define RTLD_NOW 2
typedef int (*__libc_start_mainF)(int (*)(int, char**, char**), int, char**, void (*)(void), void(*)(void), void (*)(void), void (*));
static void None(void) { }
static int Main(int a, char** b, char** c) { char msg[]="ok\n"; Linux3(1, 1, (long)msg, 3); Linux1(231, 0); }
void _start(void) {
dlopenF dlopen;
dlopen = (dlopenF)dlsym(0, "dlopen");
void* libc = dlopen("libc.so", RTLD_NOW);
__libc_start_mainF __libc_start_main;
__libc_start_main = (__libc_start_mainF)dlsym(libc, "__libc_start_main");
__libc_start_main(Main, 0, 0, None, None, None, 0); }
This can be built with,
gcc -std=gnu99 src.c -ldl -Os -fno-unwind-tables -fno-exceptions -nostdlib
Even given the unnecessary garbage GNU ld dumps in the ELF, this binary will be under 3K, which almost half the size of compiling "void main(void) { }" using the standard libs. Going to assembly or some other custom language and manually building the ELF can make this much smaller.