Размер исполнимого файла обычной программы Hello World при сборке в моей системе (X86-64, Linux) после выполнения strip составляет 6208 байтов. Для ARM Linux (ARMv9) — 2720 байтов. Можно ли улучшить эти результаты?
#include <stdio.h> int main(int argc, char ** argv) { printf("Hello!\n"); return 0; }
Очевидно, что свой вклад в размер исполнимого файла вносит библиотека glibc, из которой мы берем функцию printf(). Попробуем обойтись без нее:
int main(int argc, char ** argv) { char * s = "Hello!\n"; __asm__ ("movl $4, %%eax; movl $0, %%ebx; movl $7, %%edx; int $0x80" : : "c"(s) : "%eax", "%ebx", "%edx"); return 0; }
Последовательность ассемблерных команд в этом примере выполняет системный вызов syscall_write с аргументами 0 (стандартный поток вывода), s и 7 (длина строки s без конечного 0). Однако после компиляции этого варианта программы мы обнаруживаем, что размер исполнимого файла не изменился. Проблема в том, что библиотека glibc по-прежнему присутствует в нашей программе. Схематически структуру программы Linux можно представить так:
void _start() { // подготовка к вызову main() int retval = main(); // Вежливое завершение _exit(retval); }
Точкой входа в программу является функция _start(), которая готовит программу к вызову main(), а после выхода из этой функции завершает программу системным вызовом exit(). Мы можем сами написать функцию _start:
void _start() { char * s = "Hello!\n"; __asm__ ("movl $4, %%eax; movl $0, %%ebx; movl $8, %%edx; int $0x80" : : "c"(s) : "%eax", "%ebx", "%edx"); __asm__ ("movl $1, %eax; xorl %ebx, %ebx; int $0x80"); // Вызов, эквивалентный _exit(0) }
Теперь наша программа не использует функций glibc, но при попытке ее скомпилировать компоновщик пожалуется, что у нас определено две функции _start(). Исправить это можно, вызвав gcc с ключом -nostdlib. В результате (при компиляции с ключом -O2 и после обработки strip’ом) получим исполнимый файл программы размером 1082 байта. Чуть больше одного килобайта! Вряд ли на 64-битном процессоре можно добиться более компактного результата. ;)
А вот, например, та же программа для ARM Linux. Программа написана для ARM mode (32-битные инструкции)
void _start() { char * s = "Hello\n"; __asm__ ("mov r0, #0; mov r1, %[sptr]; mov r2, #7; swi 0x900004" // _write(0, s, 7); : : [sptr] "r" (s) : "r0", "r1", "r2"); __asm__ ("mov r0, #0; swi 0x900001"); // _exit(0); }
Программу, естественно, тоже нужно собирать с ключом -nostdlib. Получается исполнимый файл длиной 538 байтов, что и не удивительно, если учесть, что плотность кода ARM выше, чем плотность кода X86-64.