为什么像Windows和Linux这样的汇编语言有差异?

28

我对低级语言和汇编语言等方面还比较陌生,想要了解更多细节。为什么Linux和Windows的汇编语言有差异?

据我所知,当我编译C代码时,操作系统实际上并不会生成纯机器码或汇编代码,而是生成依赖于操作系统的二进制代码。但为什么呢?

举例来说,当我使用x86系统时,CPU只能理解x86汇编语言,对吗?那么为什么我们不编写纯x86汇编代码,为什么会有基于操作系统的不同汇编变体?如果我们编写纯汇编代码或操作系统生成纯汇编代码,那么操作系统之间就不会存在二进制兼容性问题,对吗?

我真的很想知道这些背后的原因。任何详细的答案、文章或书籍都会很棒。谢谢。


3
唯一的区别在于:您使用的方法取决于操作系统。例如:C语言中的printf()函数之所以会产生不同的结果,是因为每个操作系统处理方式不同。 - Erhard Dinhobl
9个回答

25

没有区别。如果处理器相同,汇编代码也是相同的。在Windows上编译的x86代码与Linux上的x86代码可以二进制兼容。编译器不会产生依赖于操作系统的二进制代码,但它可能会将代码打包成不同的格式(例如PE vs. ELF)。

区别在于使用哪些库。要使用操作系统的功能(例如I/O),必须链接操作系统的库。毫不奇怪,Windows系统库在Linux机器上不可用(除非您安装了Wine),反之亦然。


为什么会有不同的汇编语言,如Windows Asm、Linux Asm、Dos Asm? - caltuntas
基本上,没有区别。区别在于资源的使用方式(基本上是操作系统调用),而不在指令集中。 - Piskvor left the building
5
@mcaaltuntas:没有所谓的“Linux汇编”或“Windows汇编”,只有x86汇编、x86_64汇编、MIPS汇编、m68k汇编等。 - Michael Lowman
1
@mcaaltuntas:二进制代码没有区别,操作码是相同的。不同之处在于助记符形式的语法,即mov %eax,%ebx(AT&T)和mov ebx,eax(Intel)具有相同的二进制形式。 - BlackBear
从理论上讲,编写一些汇编代码作为可执行文件是可能的(例如仅执行一些非常基本的系统功能),并且可以在Windows、Mac和Linux上同样运行(不考虑任何系统库)吗? - B''H Bi'ezras -- Boruch Hashem
@bluejayke 所谓的“一些非常基本的系统函数”取决于系统库,甚至包括 exit()。如果你想要与操作系统无关的汇编语言,则需要直接引导它,绕过任何操作系统。 - OrangeDog

13

你不能直接运行纯汇编代码。代码必须以某种可执行格式存在:Windows使用PE格式,大多数Unix现在使用ELF格式(尽管也有其他格式,例如a.out)。

基本的汇编指令是相同的,并且你使用它们创建的函数也是相同的。

问题在于访问其他资源。处理器非常擅长计算,但无法访问硬盘、向屏幕打印字符或连接到蓝牙手机。这些元素总是以某种操作系统相关方式存在。它们通过系统调用来实现,其中处理器向操作系统发出信号执行某个任务。在Linux上的任务17不一定在Windows上是任务17;它们甚至可能没有等价物。

由于大多数库在其最低层级别上都有一些系统调用,所以在大多数情况下代码不能只是重新编译。


+1 我相信这是其中一个主要原因:不同的可执行文件格式。 - karlphillip

8

3

除非您使用嵌入式系统开发环境,否则您将使用针对特定运行时的编译器进行编译。该运行时定义了硬件使用的约定:参数传递、异常处理等。这些约定与操作系统交互,或者至少与程序需要链接的可用运行时库交互。


3
历史上,Linux汇编通常使用AT&T语法,因为这是GNU Assembler支持的语法。同样,Windows汇编器倾向于使用Intel语法,例如MASMNASM
所有x86汇编器都会产生相同的输出--即x86机器码。您可以在Linux上使用NASM或GNU汇编器以Intel语法进行编程,并在Windows上使用GNU汇编器以AT&T语法进行编程。

2
GNU 工具(包括 gdb)在当前版本中支持 at&t 和 intel 语法。我通常设置为 intel 语法;这可以使一些事情更容易,特别是同时使用 Ida Pro 等工具时。 - Michael Lowman

2
汇编语言与操作系统无关,而是与CPU架构相关。但操作系统中有一系列已经编译成二进制的系统功能,你可以通过中断调用来调用这些功能,例如标准输入输出,运算等。

1
操作系统决定两件事情:(1) 调用约定,它定义了参数如何进入堆栈,从而影响汇编代码;(2) 运行时库实现了常见函数,如内存分配、输入/输出、高级数学等。
因此,尽管在x86处理器上,x+y 在Windows或Linux下编译成相同的汇编代码,但由于不同的调用约定和不同的数学库,y = sin(x) 将是不同的。
除此之外,汇编语言本身也依赖于处理器。x86、x86_64、ARM、PowerPC,每种处理器都有自己的汇编语言。

1

汇编语言在 x86 架构下没有区别(尽管汇编器之间可能存在差异,因此使用的符号不同)。Linux 和 Microsoft Windows 都可以运行在其他架构上,但 Linux 更加通用。

然而,现代操作系统不只是将程序加载到内存中并运行。它提供了大量服务,并保护程序之间互相干扰。如果需要进行除基本计算以外的操作,通常必须通过操作系统来完成。(这在旧的操作系统(如 MS-DOS 和 CP/M)中不太适用,因为它们可以加载独立运行的程序,但现代非嵌入式系统几乎都有现代操作系统。)

程序也不是以纯二进制块的形式存储的。通常需要与其他库进行链接,通常是在程序被加载执行时进行(例如 DLL 的工作方式),并且必须与操作系统进行链接。操作系统可能需要其他信息,因此可执行文件中必须包含关于二进制块的某些信息。这在不同的操作系统之间会有所不同。

因此,可执行文件必须以一种格式加载到内存中,这在不同的操作系统中有所不同。为了执行任何有用的操作,它们必须进行操作系统调用,这在不同的系统之间是不同的。这就是为什么你不能将Windows可执行文件和相关库在Linux上运行的原因。

1
存在一些适用于各种平台的汇编器,可以根据源文件直接生成可在特定地址加载的输出二进制文件。这样的汇编器在一些小型微控制器或历史处理器(如6502和Z80)中很流行。在汇编程序时,需要知道它预期存储的地址;更改地址将要求重新进行汇编。另一方面,在这样的系统中进行汇编是一个单步过程。运行汇编器并获得可执行输出。在某些情况下,源代码、汇编器和输出都可以同时保存在内存中(在我的Commodore 64上,我使用了一款像这样工作的由Compute's Gazette杂志发表的汇编器)。
尽管每次地址更改时重新组装所有内容可能对于一个将“接管机器”的程序是实用的,但在许多情况下,使用多步骤过程更为理想,其中源文件被处理成包含已汇编指令以及各种“符号”信息的目标代码文件;然后以各种方式处理这些文件,以产生可直接加载到内存中的内存映像,或者是可重定位的对象文件,操作系统的加载器将知道如何调整它所加载到的任何地址。
为了使目标链接系统有用,必须允许推迟某些类型的地址计算,直到程序被链接或加载。一些系统只允许在链接/加载时执行极其简单的计算,而其他系统则允许进行更复杂的计算。当可行时,较简单的方案可能更有效率,但它们的限制可能会强制采取变通措施。例如,将使用BX循环遍历小于256字节的数据结构的例程可能会写成以下形式:
    mov bx,StartAddr

lp: mov al,[bx] ... 进行一些计算 inc bx cmp bl,<(StartAddr+Length) ; < 前缀操作符表示“最低有效位” jnz lp

使用 cmp bx,(StartAddr+Length) 是可行的,但如果编译工具支持它,仅比较低字节会更快。另一方面,一些16位汇编/链接工具可能要求在代码中存储16位地址来完成所有地址修复。

因为不同的系统在其目标代码格式中允许不同的特性,所以需要不同的汇编语言特性来控制它们。指令集可能由芯片制造商指定,但用于表达可重定位地址计算的特性通常不是。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接