【问题标题】:Can this Linux / 32bit x86 assembly "Hello, World" be made smaller still?这个 Linux / 32 位 x86 程序集“Hello, World”还能做得更小吗?
【发布时间】:2011-09-15 16:33:43
【问题描述】:

以下 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(节省一个字节)?
  • jmp 偏移8 位到已知位置(或通过汇编器/链接器调用魔术创建)以包含exit(...) 系统调用的必要指令,在xchg; int 序列上节省一个字节?

否则,是否可以证明这实际上最小的良好行为(没有崩溃/返回代码为零)Linux/x86“Hello, World”?

编辑

为了澄清,问题不是关于最小化 ELF 可执行文件的大小;这方面的技术早已为人所知。我明确询问了一个 Linux 32 位 x86 汇编程序的大小,该程序执行的编译代码相当于:

int main(int argc, char **argv)
{
    puts("Hello, World");
    exit(0); /* or whatever code */
}

会的。
事实上,我会为不需要手动编辑 ELF 标头的任何内容感到高兴。如果你找到一种方法,例如将"Hello, World" 填充到某个 ELF 对象中并从汇编源引用该对象,仅使用汇编器/链接器命令行和/或映射文件输入,我认为它足够有效,即使 增加 ELF 可执行文件的大小。我只想知道之后打印“Hello, World”和exit()的指令序列是否还能缩小。
问题是关于代码大小,而不是可执行文件大小

【问题讨论】:

标签: assembly code-golf


【解决方案1】:

早在 1999 年就完成了。看看this page(剧透:最终结果是一个 45 字节的 ELF 文件)。请务必阅读the postscript

【讨论】:

【解决方案2】:

使用 libc 直接翻译 C 代码会产生 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 个字节的代码。

【讨论】:

  • 在您使用 RET 的 32 位代码中,您是否记得调整堆栈以考虑您在 puts 之前推送的字节数?
  • 这个问题是你没有计算你链接的所有 C 运行时库代码。
猜你喜欢
  • 1970-01-01
  • 2021-07-24
  • 1970-01-01
  • 2013-03-06
  • 2017-06-10
  • 1970-01-01
  • 2015-05-04
  • 2013-09-04
  • 2014-05-17
相关资源
最近更新 更多