Actually Portable Executable(可移植可执行)

xiannv  1385天前

有一天,在研究旧代码时,我发现可以将Windows可移植的可执行文件编码为UNIX第六版shell脚本,因为Thompsonshell没有使用shebang行。当我意识到可以合成Unix、Windows和MacOS使用的二进制格式时,我就忍不住要实现它,因为这意味着高性能的本机代码几乎可以像web应用程序一样轻松。下面是它的工作原理:

MZqFpD='

BIOS BOOT SECTOR'

exec 7<> $(command -v $0)

printf '\177ELF...LINKER-ENCODED-FREEBSD-HEADER'>&7

exec "$0" "$@"

exec qemu-x86_64 "$0" "$@"

exit 1

REAL MODE...

ELF SEGMENTS...

OPENBSD NOTE...

NETBSD NOTE...

MACHO HEADERS...

CODE AND DATA...

ZIP DIRECTORY...

我开始了一个叫做Cosmopolitan的项目,它实现了Actually Portable Executable的格式。我之所以选择这个名字,是因为我喜欢能够自由地编写超越传统界限的软件。我的目标是帮助C语言成为一种“一次构建,到处运行”的语言,适合新手开发,同时避免任何可能阻止软件在技术社区之间共享的理想。以下是入门的简单方法:

gcc -g -O -static -fno-pie -no-pie -mno-red-zone -nostdlib -nostdinc -o hello.com hello.c \
  -Wl,--oformat=binary -Wl,--gc-sections -Wl,-z,max-page-size=0x1000 -fuse-ld=bfd \
  -Wl,-T,ape.lds -include cosmopolitan.h crt.o ape.o cosmopolitan.a

在上面的一行代码中,我们基本上重新配置了Linux上的stock编译器,使其输出可以在MacOS、Windows、FreeBSD、OpenBSD和NetBSD上运行的二进制文件,它们也从BIOS启动。请注意,这是为那些不关心桌面gui,只想要stdio和sockets而不需要devops的人准备的。

平台无关的C/C++ / FORTRAN工具

谁能预料到跨平台本地构建会如此简单?事实证明,它们也出奇的便宜。即使有所有magic numbers,win32 utf-8的polyfills和bios引导加载程序的代码,exe最终仍然比Go Hello World小100倍:

life.com is 12kb (symbols, source)

hello.com is 16kb (symbols, source)

请注意,zsh与ThompsonShell [update 21-02-15: zsh现在已被修补] 有一个小的向后兼容性故障,所以尝试sh hello.com而不是 ./hello.com。撇开这一点不谈,如果这么简单,为什么以前没人这么做过?我能给出的最佳答案是,它需要对ABI进行轻微的更改,其中与系统接口相关的C预处理器宏需要是符号化的。这几乎不是个问题,除了switch(errno){caseEINVAL:…}。如果我们愿意改变规则,那么GNU链接器可以很容易地配置为在linktime生成我们需要的所有PE/Darwin数据结构,而不需要任何特殊的工具链。

PKZIP可执行文件是很好的容器

单文件的可执行文件是最好的。在一些情况下,依赖于系统文件的静态可执行文件是有意义的,例如zoneinfo。然而,如果我们构建的二进制文件也打算在多个Windows支持的发行版上运行,我们就不能做出这样的假设。

事实证明,PKZIP被设计成把它的魔法标记放在文件的末尾,而不是开头,因此我们也可以用ZIP来合成ELF/PE/MachO二进制文件!我能够在Cosmopolitan代码库中有效地实现这一点,使用几行链接器脚本,以及一个用于增量压缩部分的程序。

可以运行unzip -vl executable.com来查看它的内容。Windows 10也可以将文件扩展名改为.zip,然后在微软捆绑的ZIP GUI中打开它。拥有这样的灵活性,可以在编译后轻松地编辑资产,这意味着我们还可以做一些事情,比如创建一个易于分发的ja vasc ript解释器,通过zip反射式地加载解释过的源代码。

hellojs.com is 300kb (symbols, source)

Cosmopolitan还使用ZIP格式自动遵守GPLv2[更新2020-12-28:APE现在获得ISC许可]。默认情况下,非商业的libre构建被配置为嵌入hermetic make mono repo中链接的任何源文件。这使得二进制文件大约要大10倍。例如:

life2.com is 216kb (symbols, source)

hello2.com is 256kb (symbols, source)

摇滚音乐家对动态范围压缩有着爱恨交加的关系,因为它消除了他们音乐的复杂性,但为了听起来更专业,这是必要的。Bloat也可能遵循同样的原理,在这种情况下,为了获得非经典软件消费者的青睐,嵌入zip源文件可能是一种更有社会意识的浪费资源的方式。

x86-64 Linux ABI是一种很好的通用语言

直到最近,计算机历史上才出现了硬件架构的大洗刷,500强榜单就是最好的证明。在手机、路由器、大主机和汽车之外,围绕x86的共识是如此强烈,以至于我把它比作巴别塔。多亏了Linus Torvalds,我们不仅在架构上有了共识,而且在程序通过系统调用指令与主机通信的输入输出机制上也几乎达成了共识。

所以我认为现在是对系统工程持乐观态度的最好时机。我们比以往任何时候都更愿意分享共同的东西。还有一些例外,比如我们在新闻中听到的苹果和微软的计划,他们试图将个人电脑转向ARM。我不知道为什么我们需要c级的Macintosh,因为x86_64专利今年就要到期了。苹果可能无需支付版税就可以生产自己的x86芯片。我们一直梦想的自由/开放架构,可能会成为我们正在使用的架构。

如果微处理器架构的共识最终存在,那么我认为我们应该专注于构建更好的工具,帮助软件开发人员从中受益。我一直致力于在该领域做出贡献的方法之一是构建一种更友好的方式来可视化x86-64执行对内存的影响。应该阐明Actually PortableExecutable的工作原理。

微信截图_20210301202701.png(视频地址:https://storage.googleapis.com/justine/emulator2.mp4

您将注意到,执行开始时将Windows PE头文件视为代码。例如,ASCII字符串“MZqFpD”解码为pop%r10;jno 0 x4a;jo 0x4a和字符串“\177ELF”解码为jg 0x47。然后它跳转到mov语句,该语句告诉我们程序是从用户空间运行的,而不是被引导,然后跳转到入口点。

然后,可以使用分散的部分和GNU Assembler.sleb128指令轻松地为主机操作系统解包Magic numbers。像UNICODE位查找表这样的低熵数据通常可以使用103字节的LZ4解压缩器或17字节的运行长度解码器进行解码,使用Intel的3kb x86解码器可以很容易地实现运行时代码变形。

请注意,这个模拟器不是必需的。Actually Portable Executable在shell、NT命令提示符下运行,或者从BIOS中启动都可以正常工作。这不是JVM,只有在需要时才使用模拟器。例如,能够对程序执行如何影响内存进行可视化是很有帮助的。

知道我们编写的任何普通的电脑程序都能在Raspberry Pi和Apple ARM上“正常工作”是件好事。我们所要做的就是将上述模拟器的ARM构建嵌入到我们的x86可执行程序中,并让它们适当地变化和重新执行,类似于Cosmopolitan已经在使用qemu-x86_64时所做的,只是不需要预先安装。折衷的办法是,如果我们这样做,二进制文件将只比Go的Hello World小10倍,而不是100倍。另一个折衷是GCC运行时异常禁止代码变形,但是我已经通过重写GNU运行时为您解决了这个问题。

使用完全模拟的可用性使x86-64-linux-gnu尽可能小巧,最引人注目的用例是,它允许普通的简单本机程序在任何地方运行,包括默认情况下的web浏览器。这一领域的许多解决方案都倾向于过分关注尚未达成共识的接口,如gui和线程,否则它们将只是模拟整个操作系统,如在浏览器中运行Windows的Docker或FabriceBellard。我认为我们需要兼容性粘合剂,它只运行程序,忽略系统,并将x86_64-linux-gnu作为一个规范的软件编码。

使用寿命长,无需维护

我之所以喜欢使用这些过时的技术,其中一个原因就是我希望我所从事的任何软件工作都能以最少的辛劳经受住时间的考验。就像《超级马里奥兄弟》的ROM在不需要GitHub问题追踪器的情况下存活了这么多年。

我相信最好的办法是将已经达成数十年共识的二进制接口粘合在一起,忽略api。例如,下面是Mac、Linux、BSD和Windows发行版使用的magic numbers。在您的生活中,这些数字值得至少看一次,因为这些数字是您使用过的几乎所有计算机、服务器和电话的内部结构的基础。

如果我们关注所有系统共有的数字子集,并将其与它们的共同祖先Bell系统5进行比较,我们可以看到,在过去40年里,系统工程在二进制级别上几乎没有什么变化。平台不打破自己就无法打破它们。多年来,很少有人提出UNIX命理学需要改变的观点。

下载图片.png图片.png图片.png图片.png图片.png图片.png

emulator.com(280k PE+ELF+MachO+ZIP+SH)
tinyemu.com (188kPE+ELF+MachO+ZIP+SH)

源代码

ape.S
ape.lds
bl inkenlights.c
x86ild.greg.c
syscalls.sh
consts.sh

项目

life.com(12kb ape symbols)
sha256.elf (3kb x86_64-linux-gnu)
hello.bin (55b x86_64-linux-gnu)

例子

bash hello.com #本机运行它

bash hello.com              # runs it natively
./hello.com                 # runs it natively
./tinyemu.com hello.com     # just runs program
./emulator.com -t life.com  # show debugger gui
echo hello | ./emulator.com sha256.elf

手册

SYNOPSIS
  ./emulator.com [-?HhrRstv] [ROM] [ARGS...]
DEsc riptION
  Emulates x86 Linux Programs w/ Dense Machine State Visualization
  Please keep still and only watchen astaunished das bl inkenlights 
FLAGS 
  -h        help
  -z        zoom
  -v        verbosity
  -r        real mode
  -s        statistics
  -H        disable highlight
  -t        tui debugger mode
  -R        reactive tui mode
  -b ADDR   push a breakpoint
  -L PATH   log file location

参数

ROM文件可以是ELF或一个Actually Portable Executable。

它应该按照System Five ABI使用x86_64。

系统调用ABI是在Linux内核中定义的。

特性

8086, 8087, i386, x86_64, SSE3, SSSE3, POPCNT, MDA, CGA, TTY

网站

https://justine.lol/bl inkenlights/

另请参阅

justine'sweb page

twitter.com/justinetunney

github.com/jart

本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场
来源:https://justine.lol/ape.html

最新评论

昵称
邮箱
提交评论