Можно ли сделать эту сборку Linux / 32bit x86 "Hello, World" еще меньше? - PullRequest
5 голосов
/ 15 сентября 2011

Следующая 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 ]

2 голосов
/ 15 сентября 2011

Это было сделано еще в 1999 году. Посмотрите на эту страницу (спойлер: конечный результат - 45-байтовый файл ELF). Обязательно прочитайте постскриптум .

0 голосов
/ 28 января 2017

Простой перевод кода C с использованием libc приводит к 16 байтам инструкций:

.S:
    .asciz "Hello, World"
.globl main
main:
    push $.S
    call puts
    add $4, %esp
    xor %eax, %eax
    ret

Если вместо x86-32 вы используете x86-64, соглашение о вызовах передает аргументы в регистры, чтобы мы могли пропустить манипуляции стека, и

main:
    mov $.S, %rdi
    call puts
    xor %eax, %eax
    ret

- это всего 15 байтов кода.

...