基础:
linux下的常见可执行文件有:
- 古老的 *.out 格式
- COFF 格式
- ELF 格式
- shebang 脚本
- binfmt_msic
注意:linux执行可执行文件的时候会先通过文件的头几个字节判断是什么格式,然后再做对应处理,如:
- ELF 处理器(binfmt_elf):检查前 4 字节是否为
0x7F 'E' 'L' 'F'- 脚本处理器(binfmt_script):检查前 2 字节是否为
#!- 其他:a.out、PE、Mach-O 等,或通过 binfmt_misc 注册的自定义格式。
一、古老的 a.out:极简的“汇编器输出”
a.out 是 UNIX 最古老的可执行文件格式,其名字是 “assembler output”(汇编器输出)的缩写。它的设计非常原始,为了适应早期简单的硬件(如 PDP-11),结构极其紧凑。
核心结构:文件头之后,固定包含三个段:.text (代码)、.data (已初始化数据) 和一个隐含的 .bss (未初始化数据)。
致命缺陷:无法很好地支持共享库。当 UNIX 发展到需要动态链接时,a.out 的静态结构成为了巨大的障碍。
如今,a.out 格式已在 Linux 内核 5.18 中被彻底移除,成为了计算机历史的一部分。而 GCC 默认输出文件名为 a.out 的传统,也只是那个时代留下的一个有趣的历史印记。
虽然内核移除了支持,但用户仍然可以通过 binfmt_misc 自行注册处理。
二、COFF:承前启后的“通用对象文件格式”
COFF 是 AT&T 在 UNIX System V 中引入的,旨在取代 a.out。它的核心贡献是引入了“节”(Section) 的概念,使目标文件可以包含任意数量、不同类型的节,而不再局限于固定的三个段。
关键改进:通过多个命名的节,COFF 为更灵活的链接和调试信息管理提供了基础。它也是现代 Windows PE 格式的直接基础,Windows 下的 .obj 文件和可执行文件都基于 COFF 或它的扩展。
遗留局限:尽管有所改进,COFF 仍有诸多局限,例如节名长度受限(通常不超过8个字符)、重定位信息规范不完善,以及对共享库的支持依然不够清晰和健壮。
三、ELF:现代 UNIX/Linux 的标准
ELF 是作为 COFF 的正式后继者被设计出来的,它全面解决了 COFF 的遗留问题,并成为 UNIX System V Release 4 (SVR4) 及之后所有主流 UNIX/Linux 系统的标准格式。
设计优势:ELF 提供了更清晰的链接视图(节/Section) 和执行视图(段/Segment) 分离,并拥有更完善、处理器无关的重定位规范。
原生支持:它原生、清晰地支持动态共享库,这是它取代前两者的根本原因。同时,ELF 对32位和64位系统都有良好支持,并允许更长的节名。
四、shebang
Shebang(也叫 hashbang)是 Linux/Unix 脚本文件第一行开头的 #! 两个字符,用来告诉系统:这个脚本应该用哪个解释器来执行。#叫 sharp/hash,!叫 bang,合起来就是shebang。
基本语法
#!解释器路径 [可选参数]例如:
#!/bin/bash#!/usr/bin/python3#!/usr/bin/env python3工作原理
当你直接执行一个脚本时:
./script.shLinux 内核在 execve 系统调用中会检查文件开头。如果发现前两个字节是#!,就会:
读取
#!后面的解释器路径;用该解释器来运行这个脚本;
把脚本文件路径作为参数传给解释器。
所以:
#!/bin/bashechohello等价于:
/bin/bash ./script.sh五、binfmt_misc
binfmt_misc 是 Linux 内核提供的一种可扩展的二进制格式支持机制。你可以把它理解为对#!(shebang) 机制的一种更强大、更灵活的扩展。
它允许系统管理员或用户,在不修改内核源代码的情况下,动态地“教会”内核如何识别和执行新的文件格式。
binfmt_misc 的应用非常广泛,以下是一些典型场景:
跨架构执行:通过 QEMU 用户模式模拟器,你可以在 x86 主机上直接运行 ARM、RISC-V 等架构的二进制文件。这是 Docker 构建多架构镜像、chroot 进入其他架构文件系统的核心基础。
运行 Windows 程序:注册后,可以像运行本地程序一样,直接在 Linux 下执行 Windows 的 .exe 文件,由 Wine 在后台处理。
运行 Java 程序:可以配置为直接执行 .jar 或 .class 文件,无需再手动输入 java -jar 命令。
执行脚本语言的字节码:例如,可以直接运行编译后的 Python .pyc 文件或 Emacs 的 .elc 文件。
运行古老格式:这正是我们上一轮讨论的,为 a.out 等已被内核移除原生支持的格式,通过用户空间加载器提供兼容性。