共计 2478 个字符,预计需要花费 7 分钟才能阅读完成。
行文会比拟乱,因为 TIL 次要目标是组织本人的想法而非分享。如果凑巧能帮到他人就更好了,有感兴趣的部份感觉没讲清楚的,能够留言,我能够尝试进一步阐明。
要执行一个二进制,同时须要 CPU 和操作系统的配合。
CPU
- CPU 能够解读针对某种指令集的二进制指令(比方 x86、amd64)
- 有些 CPU 反对扩大指令集,即不蕴含在指令集标准中的额定的指令。在编译二进制时如果启用了这些扩大,就能够在反对扩大指令集的 CPU 中用上这些个性。能够在运行时查看 CPU 是否反对某种扩大,所以编译器通常会提供一条不反对的状况下用于降级的分支。
OS
- 一个二进制可能会用到一些别的库,比方用了 windows api 的二进制就不能运行在 linux 上。Unix 兼容零碎中,要害的零碎 api 被标准化为 POSIX 规范。如果一个二进制只用了 POSIX api,它就能够在任何 Unix 零碎中运行,比方 Mac OS X 或 Solaris 等
二进制格局
- 除此之外,二进制必须抉择恪守某种 OS 要求的二进制格局,能力被 OS 加载、执行。如 windows 中宽泛应用的 Portable Executable format(exe 文件),Linux 中宽泛应用的 ELF format(Executable and Linkable Format)
最终,如果两个零碎:
- 有同样的 System API、零碎库
- 同样的指令集
- 应用同样的二进制格局
为一个零碎编写的二进制就能够在另一个零碎中运行。
x86 指令集的二进制能够运行在 AMD64 指令集中
- 二进制能够指定 CPU 用什么模式运行本人
有一些二进制格局容许把多个程序放在同一个文件中,每个程序针对一种不同的指令集
- 苹果从 PowerPC 架构转移到 x86 架构时就用上了“fat binaries”
有一些程序会编译成两头模式,而非最终在 CPU 上执行的二进制
- 比方 java 的字节码、v8 也有字节码的优化
- 这种计划要求在不同的指令集上实现各自的虚拟机,以运行中间代码,转换成 CPU 能运行的实在指令集
杂记
本文相干的一些概念,想到的都放这
- 当 OS 加载一个二进制的时候,它会决定二进制被如何运行。能够在编译时指定一个指标 CPU,如果不指定编译器通常应用以后的 CPU 并只应用这个 CPU 和它更低版本反对的指令集。如果你想用只在你的 CPU 上反对的新指令,则须要告知编译器,或应用汇编自行编写。但这样一来,你失去的二进制如果在不反对改指令的 CPU 上运行,就会解体。
指令集的由来
- 最后的二进制都是为特定的某一款 CPU 编写的,无奈执行在另一款中,为了解决这个问题提出了指令集,不同的 CPU 只有反对了同样的指令集,同样的二进制就能够运行在这些不同的 CPU 中
穿插编译
- 在一个零碎中为另一个不同的零碎编译二进制
- 比方在 windows 中编译出 linux 中能够运行的二进制
只有两个设施的 CPU 反对雷同的指令集,雷同的二进制就能够在两个设施上运行么
- 不是,还要看 OS 的 System API 是否兼容
- 比方同样的硬件能够装置 linux、windows 零碎,windows 中的利用就不能在 linux 中运行
指令集是啥
CPU 反对的一系列指令,比方咱们有一个 CPU 反对以下指令
- 写入寄存器 a
- 读取寄存器 a
- 写入寄存器 b
- 读取寄存器 b
- 将第一个、第二个数相加,后果写入寄存器 a
咱们写了一段代码“1×3+2=?”它为指标,编译进去的二进制就是:
- 将 1 写入寄存器 a
- 将 1 写入寄存器 b
- 相加
- 将 1 写入寄存器 b
- 相加
- 将 2 写入寄存器 b
- 相加
- 读取寄存器 a
下面的指令是能够在这个 CPU 中执行的,但如果你写了这样的二进制
- 将 1 写入寄存器 a
- 将 3 写入寄存器 b
- 相乘 <<<<
- 将 3 写入寄存器 b
- 相加
- 读取寄存器 a
- 这个二进制就不能在该 CPU 中执行,因为用到了一个它不反对的指令“相乘”
System API 是啥
- 操作系统提供的 API
常见的有
- POSIX:UNIX、LINUX、Mac OS X 等
- Win32: Windows
- Java 虚拟机提供给字节码的 API 也算是一种 System API
- 并不齐全对标,比方 Win32 中蕴含 GUI 相干的 API,创立窗口之类的,但 POSIX 中没有
假如有这样一个 OS A,他的窗口必须有,且只有一个题目,当二进制想要创立一个本人的窗口,能够调用这个 OS 提供的一个 API
create_window("Hello world")
- 这样就能创立出一个题目为 Hello world 的窗口
而后再假如另一个 OS B,它的窗口必须有题目和副标题,它提供的 API 可能就是
- `create_window(“Hello world”, “subtitle”)
- 如果在这个 OS 中执行后面的二进制,显然短少参数,它们是不兼容的
- 因而只管 CPU 能够执行二进制,但 System API 不兼容的状况下,程序仍然可能解体
wine 能够让 windows 利用在 linux 中执行,一部分原理就是翻译这种零碎调用
- 以后面的创立窗口为例,为了在 OS B 中运行 OS A 的利用,wine 这种利用可能在收到
create_window("Hello world")
时,将它翻译为create_window("Hello world", "__SPOOF__")
,这样一来 OS B 失去了本人想要的两个参数。但缺点是所有通过兼容层运行的 OS A 的利用,都会同样的副标题“__SUB_TITLE__
”。这种 System API 差别有时能够完满翻译,但有时也会像这个例子一样有缺憾。
- 以后面的创立窗口为例,为了在 OS B 中运行 OS A 的利用,wine 这种利用可能在收到
二进制格局是啥
- 把内容组织进一个二进制文件的规定
假如一个二进制格局 A,规定如下
- 结尾 N 个字节:我是格局 A、我的指标架构是 AMD64、我应用大端 / 小端、应该从哪个地址开始执行,吧啦吧啦
- 接下来 N 个字节:可执行指令的范畴
- 接下来 N 个字姐:数据的范畴
- 可执行指令
- 数据
OS 在执行这个二进制的时候会有这样的步骤
- 解读结尾 N 个字节,失去各种信息
- 创立内存空间,加载可执行指令到内存
- 从指定地址开始执行指令
后面说 POSIX 没有 GUI 相干的 API,wine 是怎么把 windows 的 gui 利用跑在 linux 中的
- wine 把 GUI 相干的 API 翻译为 X11 的
- 想独自开一篇讲 X11(QT、GTK、GNOME 等一众小弟),感觉也很有意思
参考资料
https://www.geeksforgeeks.org…