Quantcast
Viewing all articles
Browse latest Browse all 10

Самый компактный исполнимый файл для Linux

Размер исполнимого файла обычной программы 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.


Viewing all articles
Browse latest Browse all 10

Trending Articles