这个Linux / 32位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

如果愿意牺牲位置独立性(相反,强制链接器插入字符串地址),并且不关心程序返回0,则可以将其简化为:

.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可执行文件的大小;这方面的技术早已为人所知。我明确地询问了Linux 32位x86汇编程序的大小,该程序执行的代码相当于编译后的代码:
int main(int argc, char **argv)
{
    puts("Hello, World");
    exit(0); /* or whatever code */
}


事实上,我对任何不需要手工编辑ELF头文件的方法都很满意。如果您找到一种方法,例如将"Hello, World"填充到一些ELF对象中,并从汇编源引用它,仅使用汇编器/链接器命令行和/或mapfile输入,我认为它足够有效,即使增加 ELF可执行文件的大小。我只是想知道之后打印"Hello, World"和exit()的指令序列是否还可以缩小。
问题是关于代码大小,而不是可执行大小

早在1999年就完成了。看看这个页面(剧透:最终结果是一个45字节的ELF文件)。请务必阅读附言

使用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个字节的代码

相关内容

  • 没有找到相关文章

最新更新