为 a.out 举行一个特殊的告别仪式

1. v5.1 开始剔除 a.out 格式

Linux 发布 5.1, Linux Lab 同步支持 一文中,首次得知了 Linux 移除 a.out 格式的消息,这个消息着实令人感叹,因为 a.out 伴随 Linux 的诞生至今在 Linux 中有将近 ~28 年的历史,而 a.out 本身则要追溯到更早的 Unix 时代。

下面是 v5.1 中两笔剔除 a.out 的动作:

$ git log --oneline v5.0..v5.1 | grep "a\.out"
eac6165 x86: Deprecate a.out support
08300f4 a.out: remove core dumping support

第 2 笔是 Linus 亲自改的,理由是 a.out 的 core dumping 功能年久失修了,而更进一步,因为 ELF 自 1994 年进入 Linux 1.0 以来,已经 ~25 年了,而且现在基本上找不到能产生 a.out 格式的编译器,所以 Borislav Petkov 直接在 x86 上把 HAVE_AOUT “干掉”,因此没法打开配置了。

2. a.out 核心代码还在

当然,大佬们做事还留有一点余地:

Linux supports ELF binaries for ~25 years now.  a.out coredumping has

这意味着目前的 a.out 代码核心还在,想要用,把上面第 1 条变更 Revert 掉即可配置,可能涉及冲突要修复,如果嫌麻烦,对照 git show eac6165,简单加回 HAVE_AOUT 即可:

$ git revert eac6165

笔者试着恢复以后,确实可以进行 a.out 的配置了。

3. 准备一个支持 a.out 的内核

可以用 Linux Lab 来快速验证。

$ git clone https://gitee.com/tinylab/cloud-lab
$ cd cloud-lab
$ tools/docker/run linux-lab

上面的命令正常会拉起来一个浏览器,并自动登陆进一个 LXDE 桌面,进去后,打开控制台,即可参考 README.md 依次完成下述动作。

选择 i386/pc 作为测试板子:

$ make BOARD=i386/pc

并开始内核的下载、检出、配置、编译和运行:

$ make kernel-download
$ make kernel-checkout
$ make kernel-defconfig
$ make kernel-menuconfig

上述命令会启动配置,在配置里头打开:

Executable file formats --->

Kernel support for a.out and ECOFF binaries

接着完成编译并通过 nfsroot 启动:

$ make kernel
$ make boot ROOTDEV=/dev/nfs

通过 nfsroot 启动主要是方便后面在 qemu guest 和 qemu host 之间共享文件。这里也可以用 9pnet:

$ make boot SHARE=1

用 nfsroot 的话,共享目录 host 为 boards/i386/pc/bsp/root/2019.02.2/rootfs,guest 为根目录。

用 9pnet 的话,共享目录 host 为 hostshare,guest 为 /hostshare。也可以自行通过 SHARE_DIR 指定位置:

$ make boot SHARE=1 SHARE_DIR=$PWD/hostshare

4. 尝试运行 a.out 格式 

不过未来两三个版本以后,a.out 可能很快就被完全移除掉。

在 a.out 彻底被删除之前,来举行一个告别仪式吧:那就是尝试在 Linux v5.1 上跑一个真正的 a.out 格式的可执行文件。

既然 Linus 都说已经没有工具链默认能够生成 a.out 可执行文件,那怎么办呢?

4.1 你所看到的 a.out 并不是 a.out 格式

大家可能会说,gcc 默认编译生成的不就是 a.out 么?非也,此 a.out 非彼 a.out。

gcc 默认生成的 a.out 的实际格式是 ELF:

$ echo 'int main(void){ return 0; }' | gcc -x c - -
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=baede5e2d5c16ba4b13a0e9d355acad2d237f6a7, not stripped

为什么 gcc 默认把 ELF 格式的可执行文件也默认取名为 a.out 呢?这个主要是历史沿革。

a.out 作为最早的可执行文件格式,其本意是 Assembler Output 的缩写。

虽然,现如今,汇编完还加入了链接环节,甚至还有动态链接环节,伴随着地是可执行文件格式从 a.out, COFF 到 ELF 一路演化下来,但是长久以来,这个 a.out 的默认名字却保留了下来。

4.2 gcc 不行,试试 objcopy 格式转换

既然现在的 gcc 默认不支持生成 a.out 格式,那尝试用 objcopy 转换看看,发现也不成功。

$ objcopy -O a.out-i386-linux a.out a.out-elf
objcopy: a.out-elf: can not represent section `.interp' in a.out object file format
objcopy:a.out-elf[.interp]: Nonrepresentable section on output

既然不支持 .interp,那用一个不需要库函数的程序试试,Linux Lab examples/assembly/x86 下提供了这样一个汇编程序。

$ cd (linux-lab)/examples/assembly/x86
$ make
$ objcopy -O a.out-i386-linux x86-hello x86-hello-a.out
$ file x86-hello-a.out
x86-hello-a.out: Linux/i386 demand-paged executable (ZMAGIC)

不幸地是,转换成功了,但是并不能执行。

$ ./x86-hello-a.out
./x86-hello-a.out: line 1: syntax error: unterminated quoted string

这说明 ZMAGIC a.out 不知道哪天开始已经失效了。那 QMAGIC 呢,可是,目前没找到合适的方法强制转换为 QMAGIC 类型,欢迎读者们反馈补充。

关于 ZMAGIC 和 QMAGIC 的说明如下,摘自 A.OUT Manual page:

OMAGIC

4.3 试试 Linux 0.11

暂时还不该放弃努力,因为 a.out 是 Linux 一早就支持的格式,那为什么不试试 Linux 0.11,正好 Linux 0.11 Lab”(https://gitee.com/tinylab/linux-0.11-lab) 还提供了一个可以在 Linux 0.11 上运行的编译器。

4.3.1 准备 Linux 0.11 Lab

Linux 0.11 Lab 可以直接在 Linux Lab 下跑,可以在 Linux Lab 中把它也 clone 到 /labs 目录下:

$ cd /labs
$ git clone https://gitee.com/tinylab/linux-0.11-lab

4.3.2 准备 Hello.s

接着先准备好一个可以在 Linux 0.11 编译的程序,可以直接用标准的 hello.c,也可以用汇编,这里直接复用上面的 examples/assembly/x86/x86-hello.s,把它复制到 Linux 0.11 的磁盘中,并稍作改动即可:

$ make mount-hd

$ sudo cp ../linux-lab/examples/assembly/x86/x86-hello.s rootfs/_hda/usr/root/

$ sudo diff -Nubr ../linux-lab/examples/assembly/x86/x86-hello.s rootfs/_hda/usr/root/x86-hello.s
--- ../linux-lab/examples/assembly/x86/x86-hello.s    2019-04-27 03:02:26.685203102 +0000
+++ rootfs/_hda/usr/root/x86-hello.s    2019-08-17 21:28:25.000000000 +0000
@@ -1,12 +1,12 @@
 .data                   # section declaration
 msg:
-    .string "Hello, world!\n"
+    .ascii "Hello, world!\n"
     len = . - msg   # length of our dear string
 .text                   # section declaration
                         # we must export the entry point to the ELF linker or
-    .global _start      # loader. They conventionally recognize _start as their
+    .globl _main        # loader. They conventionally recognize _start as their
                         # entry point. Use ld -e foo to override the default.
-_start:
+_main:
 # write our string to stdout
     movl    $len,%edx   # third argument: message length
     movl    $msg,%ecx   # second argument: pointer to message to write

$ sync
$ make umount-hd

主要老版本 gcc 不支持 .string,需要用 .ascii 替换,另外默认入口需要改为 _main,而不再是 _start

4.3.3 在 Linux 0.11 中编译 Hello.s

之后进入 Linux 0.11 Lab 并启动 Linux 0.11,这里选择从硬盘加载文件系统,其他文件系统不带编译器:

$ cd linux-0.11-lab
$ make boot-hd

启动以后,编译并验证一下:

$ gcc x86-hello.s
$ ./a.out
Hello, world!

如果需要编辑,记得通过 mkdir /tmp 创建一个 /tmp 目录,然后就可以用 vi 直接在 Linux 0.11 编辑代码了。

4.4 在 Linux Lab 中运行 QMAGIC a.out

之后,重新把磁盘挂起,通过 9pnet 指定相应目录为共享目录,这样就可以直接在 Linux 中运行了。

$ make mount-hd
$ ls rootfs/_hda/usr/root/
a.out x86-hello.s

$ sudo file rootfs/_hda/usr/root/a.out
rootfs/_hda/usr/root/a.out: a.out little-endian 32-bit demand paged pure executable not stripped

$ cd ../linux-lab
$ make boot SHARE=1 SHARE_DIR=$PWD/../linux-0.11-lab/rootfs/_hda/usr/root/
...
# /hostshare/a.out
fd_offset is not page aligned. Please convert program: a.out
Hello, world!

4.5 试试从 a.out 到 ELF 的转换

虽然上面的 ELF 转换成 ZMAGIC a.out,无法正常运行,但是反过来呢?试着从 QMAGIC a.out 转换为 ELF,竟然可以运行成功:

$ objcopy -O elf32-i386 a.out elf-hello
$ file ./elf-hello
elf-hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
$ sudo ./elf-hello
Hello, world!

补充一下,用 objcopy --info 可以列出支持的所有格式:

$ objcopy --info
       a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om
  i386 a.out-i386-linux pei-i386 pei-x86-64 ---------- ----------
  l1om ---------------- -------- ---------- elf64-l1om ----------
  k1om ---------------- -------- ---------- ---------- elf64-k1om
 iamcu ---------------- -------- ---------- ---------- ----------
plugin ---------------- -------- ---------- ---------- ----------

需要补充一点,上面如果直接运行 ./elf-hello,会出现段错误,留待后续分解吧。

5. 小结

到这里为止,经过诸多努力,终于在 a.out 彻底被从官方 Linux 剔除之前,完成了一次运行的尝试。

在这个基础上,未来,就有机会更深度地分析 a.out,COFF 到 ELF 三种格式以及它们的演进历程。

发布了124 篇原创文章 · 获赞 334 · 访问量 72万+

猜你喜欢

转载自blog.csdn.net/juS3Ve/article/details/100569969