我在 GCC 中编译了一个小的“Hello World”程序,它在 Linux 中运行。然后我在程序中添加了 .exe 扩展名,看看它是否适用于 Windows。但它没有用。程序无法在 Windows 中运行的原因是什么。基本上二进制和汇编指令在 windows 和 linux 下编译的程序中是相同的(如果不使用操作系统特定的库)。操作系统只需将程序加载到 RAM 中,处理器就会执行它。那么,为什么它不起作用。
4 回答
我会把你的问题分解成更小的子集
我在 GCC 中编译了一个小的“Hello World”程序,它在 Linux 中运行。
这是意料之中的,因为系统上安装的常用 GCC 和 binutils 被配置为在执行它们时针对相同的目标和主机进行编译。或者用外行的话来说,它们将生成一个适用于 Linux 内核和 GNU C 标准库(或兼容的 libc)运行时的可执行文件。
还有一些编译器可以构建程序以在不同类型的操作系统和/或 CPU 架构上运行,而不是它们本身在其上执行。这些就是所谓的交叉编译器,例如,您正在使用它们在台式计算机上构建智能手机应用程序。当您使用 Windows Android NDK 创建本机 Android 可执行文件时,您实际上是在为 Linux 内核和不同的 CPU(很可能是 ARM 或 MIPS)进行交叉编译。
然后我在程序中添加了 .exe 扩展名,看看它是否适用于 Windows。
在这里,您陷入了一个误解,我永远无法理解它为什么会出现在第一位。文件名后缀必须与它可以做的任何事情有关。Windows 使用它在注册表中查找,如何处理它,但仅此而已。在 Windows 中,您可以注册任何您希望被识别为“可执行”的后缀。Linux(作为 Unixoid)做了一些更简单的事情,但恕我直言,它更强大:每个文件都带有一组标志,如果它可以执行,如果可以,谁可以执行。
但是要作为一个可以运行的程序对系统真正有用,需要一些不同的东西,并且所有可执行文件(对于给定的操作系统)共享,无论它们的文件名如何:一种特定的内部格式。Windows 使用所谓的PE
格式(PE 代表可移植可执行文件,这有点可笑,因为唯一支持它的操作系统是 Windows)。Linux 和 *BSD 使用 ELF(可执行和链接格式),MacOS X 使用称为Mach 二进制格式的东西,向 Intel CPU 的过渡扩展为Mach 通用二进制格式。从技术上讲,Mach 内核(MacOS X 的基础)也可以理解ELF格式,但在 MacOS X 中没有启用 AFAIK。
虽然这两种文件格式基本上做同样的事情(描述如何将文件加载到内存中,它依赖于哪些库,必须加载,加载它们的位置,程序的实际启动点)它们的结构非常不同. Windows 不知道如何加载和执行 ELF,Linux 不知道如何加载和执行 PE。
即使其中一个支持另一个,系统仍然会缺少程序期望加载和运行的库。至少您需要(操作系统)相关的运行时环境,它负责从操作系统检索启动参数等“平凡”的事情,为操作系统特定的内存管理功能提供接口等等。这些也从根本上说Linux 和 Windows 的区别。
请注意,完全有可能提供一个兼容性包装器,它建立在操作系统特定的东西之上,将其他操作系统模拟到程序中,并且还知道如何加载、链接和启动外部可执行文件。在 Linux 上加载和执行 Windows 程序的包装器称为 WINE,也存在称为 LINE 的相反的东西,它在 Windows 上运行 Linux 可执行文件。
但它没有用。程序无法在 Windows 中运行的原因是什么。
Windows 与 Linux 不同。它有一个完全不同的系统级 API,它使用不同的二进制格式,甚至像标准输入/标准输出(由 C 标准库定义)这样的基本功能也有很大不同的实现方式。哎呀,他们甚至使用不同的调用 约定
Linux 使用标准的 C 调用约定 (stdcall)。Windows 主要使用 cdecl 约定,它是 pascal 和 stdcall 约定的混合体。
基本上二进制和汇编指令在 windows 和 linux 下编译的程序中是相同的(如果不使用操作系统特定的库)。
呃,不,他们绝对不是。调用约定的不同会产生不同的程序集。
操作系统只需将程序加载到 RAM 中,处理器就会执行它。
没那么简单。该程序还必须“连接”到操作系统,才能做任何有用的事情。您想在控制台上看到打印的字符串吗?然后你必须将它连接到一些操作系统功能,这样做。
在 Linux 中,控制台已经存在,并且有特殊文件/dev/console
、/dev/stdin
、/dev/stdout
、/dev/stderr
,它们在内部与当前的pts
或tty
. 在进程中,它们总是映射到文件描述符 0、1 和 2。
在 Windows 中,一个程序可能需要也可能不需要打开它自己的控制台窗口(如果它是从 CLI 启动的,它将使用它)。所以它必须调用AllocConsole
,如果失败作为后备AttachConsole(parent_PID)
;然后它可以GetStdHandle
用来获取标准输入、标准输出、标准错误。this 得到的HANDLE
s 是任意的,需要连接到 C stdio(printf、fwrite 等)和 C++ iostream 的内部实现。
CPU也不会“神奇地”执行一段二进制文件,只是因为它最终在RAM中的某个地方。操作系统必须创建一个新进程,并且要做到这一点,它必须知道二进制某些重要代码所在的位置(入口点)。这些入口点(除其他外)在二进制文件格式的特殊位置进行描述。正如我已经解释过的,Windows 只理解一种与 Linux 完全不同的可执行二进制格式 (PE)。一旦二进制文件被加载和链接,它就必须真正启动。如上所述,在 Linux 和 Windows 之间启动二进制文件的步骤也不同。
后期编辑,但必须写:
*此外,了解可执行文件的二进制文件在程序启动时根本没有完全加载到 RAM 中非常重要。所发生的是,创建了所谓的“虚拟内存映射”。这意味着一个虚拟地址空间是由操作系统创建的,再加上一些其他的数据结构,这就是通常所说的进程。虚拟地址空间又由一组所谓的“页面”支持,这些页面是映射到实际内存或存储设备的地址空间块。事实上,通常 RAM总是用作磁盘操作的缓存。当程序二进制文件被映射时,虚拟内存页面将始终指向磁盘存储,而 RAM 仅充当缓存。动态分配的内存取自在存储设备上缓存交换空间的磁盘缓存。如果没有交换设备,那么它只是从通用磁盘缓存中获取的。*
如您所见,这些是完全不同的要执行的任务列表。再加上调用约定和二进制格式的差异。
那么,为什么它不起作用。
因为可执行文件不仅仅是惰性汇编指令。程序需要与操作系统对话才能产生有意义的东西。Windows 和 Linux 非常非常不同。
现代操作系统并不那么容易。如果您获取 Linux 可执行文件,添加 EXE 扩展,然后在 Windows 中运行它,它将无法工作。除了代码和数据之外,现代操作系统在可执行文件中还有很多额外的信息。Linux 使用ELF 可执行格式,Windows 使用EXE 格式。此格式包括重定位信息、内存大小、分区和有时仅与使用此可执行格式的操作系统相关的信息。所以仅仅改变文件的扩展名是行不通的。
此外,操作系统系统调用及其调用方式也有所不同。Linux 使用软件中断 (INT 0x80 IIRC) 进行系统调用,而 Windows使用另一个系统。这意味着您必须为每个 taget 重新编译,因为甚至系统调用都不是以相同的方式完成的,并且 C 编译器需要链接到该目标的正确libc。
您认为代码应该相同的假设并不完全正确。虽然数学计算的工作原理是一样的,但这是错误的,因为不同的操作系统为程序提供了相同的接口——它们没有。看: