为啥 Windows、Linux 等汇编语言之间存在差异?
Posted
技术标签:
【中文标题】为啥 Windows、Linux 等汇编语言之间存在差异?【英文标题】:Why There is a difference between assembly languages like Windows, Linux?为什么 Windows、Linux 等汇编语言之间存在差异? 【发布时间】:2011-06-04 01:40:47 【问题描述】:我对所有这些低级的东西,汇编语言都比较陌生。并且想了解更多细节。为什么 Linux、Windows 汇编语言之间存在差异?
据我了解,当我编译 C 代码时,操作系统并不会真正产生纯机器或汇编代码,它会产生依赖于操作系统的二进制代码。但为什么呢?
例如,当我使用 x86 系统时,CPU 只理解 x86 ASM 对吗?那么为什么我们不编写纯 x86 汇编代码,为什么根据操作系统会有不同的汇编变体?如果我们编写纯 ASM 或 OS 产生纯 ASM,那么操作系统之间是否不会出现二进制兼容性问题?
我真的很想知道它们背后的所有原因。任何详细的答案、文章、书籍都会很棒。谢谢。
【问题讨论】:
唯一的区别是:您使用的方法取决于操作系统。例如:C 方法 printf() 会在操作系统上做一些不同的事情,因为每个操作系统都以其他方式处理它。 【参考方案1】:没有区别。如果处理器相同,则汇编代码相同。在 Windows 上编译的 x86 代码与 Linux 上的 x86 代码二进制兼容。编译器不会生成依赖于操作系统的二进制代码,但它可以将代码打包成不同的格式(例如 PE 与 ELF)。
区别在于使用的库。为了使用操作系统的东西(例如 I/O),您必须链接到操作系统的库。不出所料,Windows 系统库在 Linux 机器上不可用(当然,除非你有 Wine),反之亦然。
【讨论】:
那么为什么会有不同的汇编语言Windows Asm、Linux Asm、Dos Asm? @mcaaltuntas:本质上,没有。区别在于资源的使用方式(基本上是操作系统调用),而不是指令集。 @mcaaltuntas:没有。有 x86 程序集、x86_64 程序集、mips 程序集、m68k 程序集...不是 linux 程序集或 windows 程序集 @mcaaltuntas:二进制码没有区别,操作码是一样的。区别在于助记符形式的语法,即 mov %eax,%ebx (AT&T) 和 mov ebx,eax (Intel) 具有相同的二进制形式。 所以理论上是否有可能编写某种可以作为可执行文件工作的汇编代码(例如,只是为了做一些非常基本的系统功能),在 windows、mac 和 linux 上同样适用? (不考虑任何系统库)?【参考方案2】:好吧,你不直接运行汇编。代码必须是某种可执行格式:windows 使用 PE,现在大多数 Unices 使用 ELF(尽管还有其他的,比如 a.out)。
基本的汇编指令是一样的,你用它们创建的函数也是一样的。
问题在于访问其他资源。处理器确实很擅长计算,但不能访问硬盘,不能在屏幕上打印字符,不能连接蓝牙手机。这些元素总是以某种方式依赖于操作系统。它们是根据系统调用实现的,其中处理器向操作系统发出信号以执行特定任务。 Linux 上的任务编号 17 不一定是 Windows 上的任务 17;他们甚至可能没有等价物。
由于大多数库在其最低级别都有一些系统调用,这就是为什么不能在每种情况下都重新编译代码的原因。
【讨论】:
+1 我认为这是主要原因之一:不同的可执行文件格式。【参考方案3】:除了其他答案。
操作系统规定其Application Binary Interface (ABI),其中包括可执行对象的格式。这些是用于 Linux(和许多其他类 Unix 系统)的 Executable and Linkable Format (ELF) 和用于 Windows 的 Portable Executable (PE)。其他格式见this table。
【讨论】:
【参考方案4】:除非您使用的是嵌入式系统开发环境,否则您正在使用针对特定运行时的编译器进行编译。该运行时定义了使用硬件的约定:参数传递、异常处理等。这些约定与操作系统交互,或至少与程序需要链接的可用运行时库交互.
【讨论】:
【参考方案5】:从历史上看,Linux 汇编倾向于使用 AT&T 语法来完成,因为这是 GNU Assembler 支持的。同样,Windows 汇编器倾向于使用 Intel 语法,如 MASM 和 NASM。
所有 x86 汇编器都产生相同的输出——即 x86 机器代码。你可以在 Linux 上使用 NASM 或 GNU Assembler 在 Intel 语法下编程,在 Windows 上使用 GNU Assembler 在 AT&T 语法下编程。
【讨论】:
GNU 工具(包括 gdb)在当前版本中支持 at&t 和 intel 语法。我通常设置 intel-syntax;它使事情变得容易得多,尤其是在同时使用 Ida Pro 等工具时【参考方案6】:Assembly 语言与 CPU 架构相关的不是操作系统,而是操作系统。有一系列编译成二进制的系统函数,你的汇编程序可以通过中断调用来调用它们。比如标准输入输出,操作ecc....
【讨论】:
【参考方案7】:操作系统决定了两件事:(1) calling convention,它定义了参数如何进入堆栈并因此影响汇编代码,以及 (2) 实现内存分配、输入等常见功能的运行时库/output,高级数学等。
因此,虽然x+y
在 x86 处理器上的 Windows 或 Linux 下编译为相同的汇编代码,但由于调用约定和数学库不同,y = sin(x)
会有所不同。
除此之外,汇编语言本身还依赖于处理器。 x86、x86_64、ARM、PowerPC,都有自己的汇编语言。
【讨论】:
【参考方案8】:只要我们坚持使用 x86,汇编语言就没有区别(尽管汇编程序之间可能存在差异,因此使用的符号也会有所不同)。 Linux 和 Microsoft Windows 都可以在其他架构上运行,在 Linux 上更是如此。
但是,当今的操作系统不仅仅将程序加载到内存中然后放手。它提供了大量的服务。由于它还相互保护程序,因此它施加了限制。要进行基本计算以外的任何操作,通常需要通过操作系统。 (对于旧操作系统,如 MS-DOS 和 CP/M 来说,情况就不那么正确了,它们可以加载独立运行的程序,但现在几乎每个非嵌入式系统都有现代操作系统。)
程序也不存储为纯二进制 blob。通常需要与其他库链接,通常是在加载程序以执行时(例如,DLL 的工作方式),并且需要与操作系统链接。操作系统可能需要其他信息,因此必须有一些关于可执行文件中二进制 blob 的信息。这因操作系统而异。
因此,可执行文件必须采用某种格式才能加载到内存中,这因操作系统而异。为了做任何有用的事情,他们必须进行操作系统调用,这在系统之间是不同的。这就是为什么您不能获取 Windows 可执行文件和相关库并在 Linux 上运行它的原因。
【讨论】:
【参考方案9】:存在一些适用于各种平台的汇编程序,给定一个源文件,它们将直接生成一个输出二进制文件,该文件被设计为加载到特定地址。这种汇编器在一些小型微控制器或一些历史悠久的处理器(如 6502 和 Z80)中很受欢迎。组装程序时,有必要知道它预期驻留的地址;使用不同的地址将需要重新组装程序。另一方面,在这样的系统中组装是一个单步过程。在源代码上运行汇编程序并获得可执行输出。在某些情况下,可以一次将源代码、汇编程序和输出全部保存在内存中(在我的 Commodore 64 上,我使用了一个汇编程序,它发表在 Compute 的 Gazette 杂志上,就是这样工作的)。
尽管在地址更改时重新组装所有内容对于“接管机器”的程序来说可能是可行的,但在许多情况下,最好使用多步骤过程,将源文件处理为目标代码文件,其中包含汇编指令,但也包含关于它们的各种“符号”信息;然后以各种方式处理这些文件,以便生成可以直接加载到内存中的内存映像,或者生成一个组合的可重定位目标文件,操作系统的加载程序将知道如何调整它可能加载到的任何地址.
为了使对象链接系统有用,它必须允许延迟某些类型的地址计算,直到程序被链接或加载。一些系统只允许在链接/加载时执行极其简单的计算,而其他系统则允许更复杂的计算。更简单的方案在可行时可能更有效,但它们的局限性可能会迫使解决方法。例如,将使用 BX 循环通过小于 256 字节的数据结构的例程可能会编写为:
mov bx,StartAddr
lp: 移动,[bx] ...做一些计算 公司 cmp bl,
可以使用cmp bx,(StartAddr+Length)
,但是如果编译工具可以支持的话,只比较低字节会更快。另一方面,某些类型的 16 位汇编/链接工具可能要求所有地址修复都使用存储在代码中的 16 位地址。
由于不同的系统允许其目标代码格式具有不同的特性,因此它们需要其汇编语言中的不同特性来控制它们。指令集可能由芯片制造商指定,但表达可重定位地址计算的特性一般不指定。
【讨论】:
以上是关于为啥 Windows、Linux 等汇编语言之间存在差异?的主要内容,如果未能解决你的问题,请参考以下文章
50 岁的 C 语言,掌控 Windows/Linux/macOS 等系统半边天!
50 岁的 C 语言,掌控 Windows/Linux/macOS 等系统半边天!
为啥一个c语言在windows下编译运行没问题,在linux下编译通过,但是运行就错误了