Можно ли сделать эту сборку Linux / 32bit x86 "Hello, World" еще меньше?
Следующая 32-битная x86-программа Linux печатает строку произвольной длины (в любом случае, насколько может быть программа) и выполняет exit(0)
впоследствии:
.global _start ; notice on entry here, all regs but %esp are zero
_start:
call .L0 ; offset == strlen, provided by your assembler
.byte 'H','e','l','l','o',',',' ','W','o','r','l','d'
.L0:
pop %ecx ; ret addr is starting addr of string
mov -4(%ecx),%edx ; argument to `call`, 4 bytes: strlen
inc %ebx ; stdout == 1
movb $4, %al ; SYS_write == 4
int $0x80
xchg %eax,%ebp ; %ebp is still zero
xchg %eax,%ebx ; SYS_exit == 1, return value == 0
int $0x80
Если кто-то готов пожертвовать независимостью от позиции (вместо этого заставить компоновщик вставить строковый адрес) и не заботиться о том, чтобы программа возвратила ноль, можно получить его следующим образом:
.global _start
_start:
movb $4, %al
inc %ebx
mov $.L0, %ecx ; this address is calculated when linking
movb $.Lend-.L0, %dl ; strlen, calculated by assembler
int $0x80
xchg %eax,%ebx
int %0x80
.L0:
.byte 'H','e','l','l','o',',',' ','W','o','r','l','d'
.Lend:
Оба из них могут быть собраны / связаны через as --32 -o x.o x.S; ld -s -m elf_i386 x.o
и работает просто отлично. Второй - 26 байт кода. Если вы разрешаете сбой после печати Hello, World
затем оставьте последние две инструкции, 23 байта. Это так низко, как я мог пойти.
Вопрос, который меня всегда беспокоил, можно ли от этого выжать еще несколько байтов? Чистая моя догадка дает следующие возможные выводы:
- Каким-то образом использовать части самого "Hello, World" как код?
- Кто-нибудь знает пригодное для использования системное пасхальное яйцо?
- обманом линкера сделать точку входа 16-битным адресом, чтобы
movw $.L0, %cx
можно использовать (сохраняет один байт)? - Сделать 8-битное смещение
jmp
в место, которое известно (или создано с помощью магии вызова ассемблера / компоновщика), чтобы содержать необходимые инструкции дляexit(...)
системный вызов, сохраняя один байтxchg; int
последовательность?
Или еще, можно ли доказать, что это на самом деле самый маленький хорошо себя ведущий (без нуля код возврата / сбой) Linux/x86 "Hello, World"?
редактировать
Чтобы уточнить, вопрос не в том, чтобы минимизировать размер исполняемого файла ELF; методы для этого давно известны. Я явно спрашиваю о размере 32-битной программы сборки Linux x86, которая выполняет эквивалент того, для чего скомпилированный код:
int main(int argc, char **argv)
{
puts("Hello, World");
exit(0); /* or whatever code */
}
сделал бы.
На самом деле, я буду рад всему, что не требует ручного редактирования заголовков ELF. Если вы найдете способ напр "Hello, World"
в некоторый объект ELF и ссылаясь на него из источника сборки, используя только командную строку ассемблера / компоновщика и / или входные данные mapfile, я бы посчитал его достаточно действительным, даже если это увеличивает размер исполняемого файла ELF. Я просто хочу знать, если последовательность команд для печати "Hello, World" и exit()
после этого можно сжать до сих пор.
Вопрос в размере кода, а не в размере исполняемого файла.
2 ответа
Это было сделано еще в 1999 году. Посмотрите на эту страницу (спойлер: конечный результат - 45-байтовый файл ELF). Обязательно прочитайте постскриптум тоже.
Простой перевод кода C с использованием libc приводит к 16-байтным инструкциям:
.S:
.asciz "Hello, World"
.globl main
main:
push $.S
call puts
add $4, %esp
xor %eax, %eax
ret
Если вы вместо этого используете x86-64, если x86-32, соглашение о вызовах передает аргументы в регистры, чтобы мы могли пропустить манипуляции со стеком, и
main:
mov $.S, %rdi
call puts
xor %eax, %eax
ret
всего 15 байтов кода.