Можно ли сделать эту сборку 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 байтов кода.

Другие вопросы по тегам