x86汇编适用于哪些CPU?

6

我已经花了几年时间使用C和Java进行编码。我喜欢如何使程序显示消息框或执行某些文件操作,并且它可以在每个(我认为?)CPU上运行而没有任何问题。尽管我从未编写过任何高级内容。

我想学习汇编语言,仅限于Windows。我相信我应该学习一些称为x86 asm的东西。

我的问题:如果我不想做太疯狂或太晦涩的事情,那么我创建的程序会在每个CPU上工作吗? 我关注的是家庭电脑或可能是服务器这样的普通计算机。

我曾经有很多人告诉我,我必须从许多可靠来源中为特定的CPU选择特定的架构,但是我随后看到的代码示例可以在几个不同的CPU上工作... 矛盾的信息!

编辑: 我只是想用汇编编写一些有趣的程序,而不必担心我的朋友约翰·多会在他的计算机上使用它时遇到麻烦。我可以轻松地用C编写一个程序,它将显示消息,ping google.com和其他任何内容,而无需担心CPU。汇编语言真的不一样吗?o.O

编辑2: 也许我感到困惑,因为我不知道在Windows中使用C编写的普通(没有花哨的)程序可以在哪些CPU上工作...只有英特尔和AMD吗?


1
IBM PC的历史演变围绕着英特尔处理器展开,从8086开始,然后经过80186、80286(常见的16位)、80386(32位扩展)、80486、奔腾(有时也称为80586),然后命名/编号不再那么简单,从这里你可能可以看出“x86”的含义。通常较新型号都是完全向后兼容的,即使是现代的英特尔i7 CPU也能在16位实模式下运行,支持所有80286指令,因此您可以从1990年的旧DOS系统启动和运行(硬件驱动程序问题会使其不切实际或失败,而不是CPU)。 - Ped7g
4
x86汇编代码可以在x86处理器上运行,但无法在ARM处理器上运行,如常见于手机中的ARM处理器。因此,您需要选择特定的架构,通常不需要选择特定的CPU型号(但您可能需要使用不同的指令集扩展)。 - Jester
1
当x86_64是“当前”时,您可以在合理的时间内重新学习基础知识,并且这种机器代码仍将在每个x86_64 CPU上(大多数Windows PC今天)(但仅在您特定的操作系统下)工作。然后,现代CPU具有许多扩展,例如SSE2是64位CPU的基线,但是还有许多可选项(具有不同世代的AVX,SSE3/4等),如果您正在尝试优化某些实际代码以提高性能,则这些选项是必不可少的,但是如果您刚开始使用汇编语言并学习基本原理,则根本不需要。没有通用的汇编语言,它总是针对CPU+OS。 - Ped7g
4
请参考 https://stackoverflow.com/tags/x86/info 中提供的链接,其中包括一些适合初学者的指南、参考资料以及高级内容和性能优化相关信息。 - Peter Cordes
关于John Doe的朋友和“我可以轻松编写C程序” - 没错,你可以轻松编写它(感谢标准C库),但不能分发二进制文件。在您的系统上生成的“.exe”文件只能在兼容计算机上运行(使用默认设置,您的编译器可能会使用来自x86 ISA的低设置),因此,如果将其构建为32位应用程序,则它将在大多数x86计算机上运行,除了一些史前时期的设备...它将无法在ARM PC上运行,除非Visual Studio具有一些多平台fat二进制目标,包括x86 + ARM的机器码。 - Ped7g
显示剩余4条评论
5个回答

9
历史上,IBM PC的发展始于英特尔处理器,从8086开始,然后经过80186、80286(仅16位),80386(32位扩展),80486,Pentium(有时称为80586),Pentium II(686有时用于此系列CPU),之后的命名/编号不再那么简单。
从中创建了“x86 ”平台名称,因为所有早期型号都以“86”结尾,并且在“86”前面的数字被修改。
“x86” CPU系列在设计新型号时具有相当特殊的方式-几乎完全向后兼容。因此,80186和80286 CPU可以按原样运行8086机器代码,它们使用所有原始8086指令,只是在其上引入了新的扩展。
第一个明显不同的CPU是80386,它引入了新的32位模式,具有32位寄存器和保护模式(286 “保护模式”基本上被忽略且仅为16位)。仍然实现了向后兼容性,因为80386在通电后将以16位模式启动,作为更快的80286并带有几个额外的指令,然后代码可以根据需要切换到32位模式(通常由现代操作系统引导程序在操作系统加载过程的早期阶段执行)。
然后,80486和下一个英特尔CPU再次向后兼容80386,只扩展了原始指令集(自80486DX CPU和Pentium以来,每个x86 CPU都默认内置浮点运算单元,尽管对于现代x86来说已过时……对于旧的80386和80486SX CPU,必须购买单独的x87协处理器芯片用于HW FPU)。
同时,英特尔试图从向后兼容性监狱中解脱出来(这使得现代x86 CPU的设计非常复杂和麻烦),通过引入全新的64位平台(“Itanium”或“IA-64”),它没有向后兼容。市场在采纳方面犹豫了很多,毫不奇怪,因为所有旧的“x86”软件都不能在上面工作。
稍后,AMD介绍了自己的64位扩展,这一次是围绕“x86”遗产设计的,类似于386扩展了286,从客户的角度来看,效果要好得多(虽然这意味着对于汇编程序员来说,它有点棘手,例如 mov eax,1 将修改整个64位的 rax 寄存器,自动将顶部32位清零等等…这些古怪的“规则”使原始32位指令集的64位扩展变得可行,并且从长远来看,它运行得非常好,即使一开始可能感觉有点hack-ish。)
现代的“x86”个人电脑包含了三种主要不同的CPU,即过时的16位80286、非常古老的32位“686”和当前的64位“x86-64”变体。就汇编代码而言,它们都共享基本指令和语法,因此如果你全面学习了“686”指令集,那么x86-64源代码对你来说将会很熟悉。只要你使用指令集的基本子集(如“686”子集),你的二进制文件就能在15-20年前的所有x86个人电脑上运行特定操作系统。
与此同时,AMD(以及Cyrix和其他试图在“x86”世界中与英特尔竞争的制造商)正在生产与英特尔兼容的CPU。有时他们会尝试引入一些扩展,例如AMD的“3Dnow”,但程序员很少使用它们,通常会被放弃。唯一的例外是当前的64位模式,它是由AMD设计的,最终英特尔不得不放弃并从AMD复制到他们的CPU中(因为他们的Itanium IA-64由于缺乏向后兼容性而未被市场接受)。
但是,如果你像游戏开发者一样,追求最大的性能,你将不得不在运行时检查CPU型号/特性,并根据指令扩展的可用性,为特定CPU提供不同的二进制变体,例如只使用SSE2指令的变体,能够在所有64位CPU上运行,以及还使用新扩展(如SSE4和AVX512)的其他变体,这将仅适用于少数消费者拥有最新CPU。
许多C编译器默认针对x86目标32或64位模式(主要取决于操作系统或项目设置),并且仅使用非常有限的指令集,如大多数情况下仅使用“686”,或者甚至没有使用SSE1/2的x86_64,因此它们的二进制文件将在任何常见的x86个人电脑上运行。尽管代码不是最优的,没有使用当前CPU的现代特性。
如果你正在针对“x86”汇编知识,那么你可以学习基本指令集,例如可能只是80386,学习原则,然后看看它是如何扩展的(跳过16位80286,那只是无用的折磨,32位模式更简单,如果你真的好奇,你可以尝试看看16位有什么不同,32位的经验会使它变得更容易一些,但它是相当无意义的)。
64位比32位模式略微复杂,但也不是那么糟糕,如果你愿意,甚至可以从那开始学习(远不如16位那么棘手)。
顺便说一下,你仍应该从C-lib开始进行I/O和win API调用,即你需要C编译器。从纯汇编语言访问Win API有些棘手(如果你只是学习汇编基础知识,这只是一个不必要的干扰),并且在现代操作系统下无法直接访问硬件,因此没有办法进行I/O而不使用OS API服务,不像16位模式下那样,那里没有保护,你可以自由地访问硬件外设。你可以从C包装器中调用asm函数,该包装器可以处理I/O、内存管理和其他与OS API相关的事情,将重点放在纯算法和asm编程上(这也是实际项目中使用汇编的方式,你不会在今天的程序中写整个应用程序的汇编代码,你会将其保留在C/C++中,并仅在具有性能瓶颈的部分中重写为ASM,之前你已经通过分析鉴别出了特定的瓶颈..没有必要编写其他部分的汇编代码,如文件读取等,太麻烦而没有任何好处)。
顺便说一下,C是可移植的语言。如果你只使用标准C库,它将在几乎任何平台上(源代码)工作,但只有在为特定的目标平台编译之后才能运行二进制文件。
Windows是一个非常小的世界,主要限于x86(有些Windows变体适用于IA-64和DEC Alpha,不能运行x86二进制文件,但这些是专业机器在专业领域使用,不为公众所知)。因此,如果你使用默认选项编译Win32 x86可执行文件,则它将(次优地)在99%的Windows计算机上运行,并仅使用有限的x86指令子集。如果你将编译器切换为使用一些现代CPU的功能,那么生成的二进制文件可能在John Doe的PC上不再工作,如果他的x86 CPU不支持你的机器代码使用的某个功能。
对于许多应用程序来说,这个默认子集已经足够了,你不需要费心去处理扩展指令。只有少数应用程序需要最大的性能,如游戏、CFD或其他科学计算...你普通的Web浏览器操作+5-10%的性能惩罚可以安全地忽略(通常在驱动程序/等等中都有一些针对你当前CPU的精细调整的代码,可以处理主要的性能需求,如解码视频/等等,因此即使使用通用的“x86”目标编译的Web浏览器也会从中受益)。
如果你只是想让它运行,就不需要生成9999个asm代码变体,基本上你只需要32位或64位变体(取决于目标,我不知道Windows世界的情况,但在现代操作系统下,你可以安全地仅针对64位进行目标设置,这相当于90+%的用户),使用基本的x86指令,这将在“任何地方”(在x86 Windows上)都能工作。
但是,除了教育目的外(在这种情况下完全可以从这里开始),使用汇编没有太多意义,因为这样的汇编性能将是次优的,因此您已经可以使用C或C ++来获得类似的结果。 对于有意义的用途,您还必须学习现代扩展功能,对可用功能进行运行时检查,并动态加载函数的正确变体,使用特定CPU类型的最佳机器代码。 这是您可能需要编写9999个相同函数变量的部分(通常4-7个变量可能涵盖大多数可用CPU,一个兼容性回退仅使用基本指令集,然后几个更专业化,如SSE3,+SSE4,AVX1 / 2,AVX512等)。 此外,真正需要最大性能的实际软件非常罕见,即使大多数较简单的游戏也完全可以使用次优二进制文件,只有在像虚幻引擎开发人员这样的前沿领域工作时,才需要关注这些微调。 大多数时候,收益不值得投资。

毕竟,现在使用的软件中有很多是使用C#,Java甚至JavaScript(或PHP..我真的提到了它吗?感觉有点不好)编写的,很明显性能不是问题,否则这中的大部分软件将在一段时间后被重写成C ++,以获得性能提升。


5
你不断声称使用C编写的程序可以在其他人的计算机上无问题地使用。但这只有在他们也运行Windows而不是Linux、OS X、OpenBSD或Solaris等其他操作系统时才成立。你必须使用不同的C库来显示窗口或进行网络I/O(“ping google.com”)。特别是“ping”,因为发送ICMP echo-request数据包通常是一个特权操作,所以非常不可移植。(例如,在Linux上,“ping”可执行文件是setuid-root的,因此它可以强制执行速率限制。)你可能会使用"system("ping google.com")"而不是在你自己的程序中使用网络套接字来完成它。更好的例子是进行HTTP请求,因为任何进程都可以打开TCP连接。
你可以编写可移植的C程序,但你肯定需要努力使它们可移植。人们需要源代码,以便他们可以为自己的系统编译它。(这就是为什么Unix有一个传统,即以源代码形式分发软件:每个人都需要使用自己版本的库为自己的系统编译它。)
编译后的二进制文件仅能在其编译目标平台上运行,例如x86-64 Windows。这样的二进制文件无法在ARM Windows或仅支持32位的x86 Windows上运行。
当使用汇编语言编写代码时,你的源代码是针对特定平台(包括CPU,而不仅仅是可用的系统调用和库)的。因此,你将为32位x86 Windows编写代码,而不仅仅是“为Windows”编写代码,使C编译器能够从同一源代码生成x86 32位或x86-64 64位二进制文件。
如果你只关心32位x86 Windows,那么这并没有太大的区别。(每台“普通”的Windows计算机都可以运行32位x86二进制文件,因此如果想要在其他Windows计算机上实现可移植性,应该制作32位x86二进制文件。)
例如,让我们使用 Godbolt编译器探索器(源代码+汇编)来查看一个微不足道的C程序在两个不同的x86平台上如何编译为不同的汇编代码:
#include <stdio.h>
int main() { puts("Hello World"); }

使用gcc -O3编译(Intel语法模式而非AT&T,Godbolt将其传递给-masm=intel),在x86-64 Linux(x86-64 System V调用约定)上将编译为此汇编代码:

.LC0:
    .string "Hello World"
main:
    sub     rsp, 8                   # align the stack before a call
    mov     edi, OFFSET FLAT:.LC0
    call    puts                     # first arg passed in RDI
    xor     eax, eax                 # eax = 0 = return value.
    add     rsp, 8                   # restore the stack
    ret

但是MSVC针对32位x86 Windows将其编译为以下汇编代码:

$SG5328 DB        'Hello World', 00H
EXTRN   _puts:PROC
_main   PROC
    push     OFFSET $SG5328
    call     _puts                    ; first arg passed on the stack
    add      esp, 4                   ; clean up args
    xor      eax, eax                 ; eax = 0 = return value.
    ret      0
_main   ENDP

汇编语法不同(MASM vs. GAS),但这不是重要的事情。重要的是,字符串字面值的指针通过堆栈(使用push)传递,而不是寄存器。
在Windows上,puts的汇编符号名称以_为前缀,但在Linux上没有。
由于这是32位代码,堆栈槽的宽度仅为4个字节,而不是8个字节。
这适用于ISO C函数(如puts),可在两个平台上使用。在Windows上,您可以在WinAPI DLL中调用MessageBoxA,但Linux没有“本地”图形API。大多数桌面需要X11库,但并非所有人都使用X11。

既然您已经了解C语言,查看编译器输出是学习汇编语言的好方法。请参阅Matt Godbolt在CppCon2017的演讲{{link1:“最近我的编译器为我做了什么?揭开编译器的盖子”}}。(此外,有关编写编译为有趣汇编代码的小函数的更多建议:如何从GCC / clang汇编输出中删除“噪音”?)。


你让 Linux 的示例使用了 Intel 语法,而不是 GAS。 - Ped7g
1
@Ped7g:我甚至没有想过这个问题。不过,这确实是GAS语法,注意.string。它是GAS的.intel_syntax noprefix。我通常在Intel语法模式下使用Godbolt,这样在NASM源代码、Intel手册和编译器输出之间切换时就不需要进行太多的心理上下文切换了。我通常只在GCC错误报告或回答使用AT&T语法的SO问题时才使用AT&T语法。无论如何,比起mov $.LC0, %edi,两个类似MASM的块都使用OFFSET symbol_name更容易发现差异,所以我认为这样做更好。 - Peter Cordes
是的,我同意保持原样,你说的“.string”部分是GAS语法,但英特尔风格... :D ...谈论x86汇编语法真是一种乐趣...只有大约100个不同的变体... :D (甚至在涉及平台差异之前)。 - Ped7g

3
让我们一步一步来:
  • 如果处理器相同,则asm代码相同,操作系统不重要,因此您不必专注于Windows。
  • 是的,您必须选择特定的架构(或者应该这样做)。每种架构都有不同的指令集。此外,您必须了解架构(寄存器、地址、总线等)以设计适当的程序,因此肯定是非平凡的。
  • 汇编不容易,实际上非常困难,因此如果目标是复杂的任务(服务器?),那么困难变得几乎不可能。如果这是一个问题(或架构),您可以尝试更高级别的语言,如C。
  • 在不同的体系结构中运行二进制文件可能会起作用吗? 是的,但这只是巧合(或兼容,如Intel 8085上的Intel 8080)。

2
8080与x86不具备二进制兼容性。如果它具有二进制兼容性,那么它将成为x86。请参阅:x86的起源:Intel 8080 vs Intel 8086? - Peter Cordes
2
汇编只有在你不了解它的时候才会变得困难。如果你同样熟练掌握C和汇编,那么在汇编中实现函数并不比C更难,只是需要更多时间。(特别是如果你想让它运行得更快。)当然,你不希望用汇编开发大型程序,因为编译器在这个规模上的优化要比人类写可维护代码时更好。 - Peter Cordes
@PeterCordes 是的,完全正确。我已将8000编辑为8085。感谢您的提醒。 - Jose
2
对于一个完全的初学者,确保你所跟随的教程是针对Windows的是必要的。系统调用是特定于操作系统的,因此你不能只是找到一些带有DOS“int 21h”或Linux或OS X“int 0x80”调用的代码来进行输入/输出,并期望它能够工作。它会被汇编(如果语法相同),但在运行时会崩溃。甚至在Windows x64和x86-64 System V之间,调用约定也不同(阴影空间、参数传递寄存器以及哪些是调用保留的)。 - Peter Cordes

2
汇编语言不会在CPU上运行,需要通过汇编程序将其转换为机器码。这个转换通常很简单(汇编程序将包含汇编代码的文本转换为某些目标代码;链接器将多个目标文件聚合成一个可执行文件,并解决重定位问题;该可执行文件包含机器码)。 x86有几种汇编语法(例如nasmgas)。
同时,x86有几个变体(例如一些处理器接受AVX等扩展,以及32位和64位)。
最后,针对 Windows 的用户空间 x86 程序是一些可执行文件。它不能在运行其他操作系统(如 Linux)的相同 x86 架构(甚至是相同硬件)上工作,因为ABI系统调用和可执行文件格式(Windows 上的 PE,Linux 上的 ELF)都不同。阅读操作系统:三个简单部分以了解更多关于操作系统的信息。

NASM在Windows上能用吗,还是应该使用MASM? - securityauditor
2
@securityauditor NASM可以在Windows上运行,查看其发布文件夹https://www.nasm.us/pub/nasm/releasebuilds/2.14rc5/。 - xmojmr
2
@securityauditor 尽管NASM也有适用于Windows的二进制文件,并且在那里运行良好,但整个Windows世界通常都集中在MS开发工具上,因此使用带有MASM的Visual Studio可能会更简单一些。尽管由于ASM开发现在已经很少见了,所以教程/工具可能不再那么友好,实际上在Windows上设置NASM + clang或gcc可能会更容易...(我不知道,十年没见过Windows了,请自行研究)。最后,对我来说,为什么要费心去处理旧版Windows,因为您可以免费使用任何其他现代操作系统。 - Ped7g
1
@securityauditor - 你没说你现在是如何在Windows上进行编程的,但如果你恰好使用Visual Studio编写C代码,它内置支持asm语言 - 无需安装任何东西。如何在Visual Studio中启用汇编语言支持 - Bo Persson
3
@securityauditor - 1) 显然这是一个微软产品,因此它们使用MASM格式。2) 跑题了 - 你写汇编代码是因为你想做一些C编译器不能或不会为你的特定CPU做的非常特殊的事情,例如使用某些非常特定的指令。所以代码无论如何都不可移植。写汇编有点“有点疯狂” :-) - Bo Persson
显示剩余4条评论

0
汇编语言在每个CPU上都有许多部分,但是对于不同的CPU来说,有一些东西是不同的,因为汇编语言直接与CPU通信(与高级编程语言不同)。要理解其中原因,必须先了解CPU。
CPU具有用于处理的临时存储。其中一些存储是用于特定目的的,但大多数都用于任何事情。不同的CPU允许存储不同数量的数据在这些容器中。此外,某些CPU的存储容器较少。x86 CPU的存储容器和处理能力没有x64 CPU那么多。
在x64 CPU上,你可以直接相加1084848和10487583848(这些数字完全是随机的),但在x86 CPU上,你可能需要将这个问题分成多个部分。
在x64 CPU上,.long可以存储8字节(64位),但在x86 CPU上,.long可能无法存储8字节。
此外,所有这些都有例外,因为并非所有的x86系统都是相同的。如上所述,有些x86使用16位,有些同时使用16位和32位。

你可能想要了解更多信息,请访问:https://archive.org/details/ost-computer-science-programminggroundup-0-9/page/n26/mode/1up?view=theater 以及: https://nixhacker.com/getting-processor-info-using-cpuid/amp/

只要它在我的电脑上能运行,我就不会太担心,因为从技术上讲,没有任何系统编译相同,并且许多功能在其他系统上无法正常工作。如上所述,你的 C 程序不能在每个系统上运行。这就是为什么微软制作了多个版本并将其标记为它们所针对的操作系统。

也许你不应该太担心你的程序是否在所有设备上都能运行,除非你想制作多个版本。


一个x64 CPU的存储容器和处理能力都不如x86 CPU。x86-64 CPU比在32位模式下运行的x86 CPU拥有更多和更宽的寄存器。这就是为什么在32位x86上添加64位整数的示例需要更多的工作,如果你不使用MMX或SSE2的原因。此外,在GAS语法中,.long指令对于i386和x86-64来说都是4个字节。实际上,它只是.int的同义词。这些指令确实因目标而异,但i386和x86-64恰好是一致的。 - Peter Cordes
说“.long”和我没有说它们不同时,“.int”是一样的,这完全没有意义。这就像说我是正确的一样。 - user20726500
按照这种推理,“2可能不等于2”在技术上是正确的陈述,但它并不实用。最好选择一个在64位和32位模式下GAS语法中确实不同的示例。但是我想不出任何关于GAS指令的例子;我认为.anythingas --32as --64中的大小相同。但是,在64位模式下使用.long foo而不是quad foo(其中foo是符号名称)可能会导致链接错误;地址是64位的。 - Peter Cordes
不要编辑添加“另外,我在技术上交换了x86和x64。”你应该修复原始段落,以表达它一直应该表达的意思,而不是让未来的读者感到困惑,直到他们读到最后。如果有人想查看,编辑历史记录就在那里。 - Peter Cordes

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