【发布时间】: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()的指令序列是否还能缩小。
问题是关于代码大小,而不是可执行文件大小。
【问题讨论】:
-
你想闯入哪里? ;-)
-
@Luigi Plinge:准时打败我;)
-
请您在我们的新Code Golf 上问这个问题吗?
-
我投票结束这个问题,因为它是代码高尔夫。