sec*_*tor 3 x86 assembly x86-64 masm nasm
我已经用C和Java编写了几年代码。我很喜欢如何使程序显示消息框或执行一些文件操作,并且该程序可以在每个(我认为?)CPU上运行而不会产生任何麻烦。尽管我从未编写过任何高级代码。
我想学习汇编,仅适用于Windows。我相信我应该学习x86 asm。
我的问题:如果我不想做太疯狂或晦涩的事情,我创建的程序是否可以在每个CPU上运行?我的重点是您家里的普通计算机或服务器。
我有很多人告诉我,我必须从许多著名的来源中为特定的CPU选择特定的体系结构,但是随后向我展示了可在多个不同的CPU上工作的代码示例...冲突的信息!
编辑: 我只想编写一些有趣的汇编程序,而不必担心我的朋友John Doe在计算机上使用它时会遇到麻烦。我可以很容易地用C编写一个程序,该程序将显示一条消息,对google.com等进行ping操作,而不必担心CPU。asm真的不一样吗?O
EDIT2: 也许我感到困惑,因为我不知道Windows中用C编码的普通(没什么花哨的)程序可以在哪些CPU上工作……仅Intel和AMD?
IBM的PC周围的英特尔处理器历史上形成,从80 86,然后再通过801去86,802 86(仍16位只),803 86(32位扩展),80486,奔腾(有时被称为80586),奔腾II (686有时用于该系列CPU),然后命名/编号就不再那么简单了。
通过这些名称,创建了“ x 86 ”平台名称,因为所有早期模型的名称都以“ 86”结尾,并且修改了86之前的数字。
“ x86”系列CPU在新模型的设计方式上非常具体-几乎完全向后兼容。因此80186和80286 CPU可以按原样运行8086机器代码,它们使用了所有原始的8086指令,并在此之上引入了新的扩展。
第一个截然不同的CPU是80386,它引入了具有32位寄存器和保护模式的新32位模式(286“保护模式”通常被忽略,仅为16位)。向后兼容性仍然得以实现,因为加电后的80386将以16位模式启动,以更快的80286速度运行,并带有少量额外的指令,然后代码可以根据需要将其切换为32位模式(通常现代OS Bootloader将,非常早在操作系统加载过程中)。
然后80486和下一个Intel CPU再次与80386向后兼容,只是扩展了原始指令集(并且自从80486DX CPU和Pentiums开始,每个x86 CPU现在默认都内置了浮点单元,尽管现代x86已经过时了。对于较旧的80386和80486SX CPU,必须为硬件FPU购买单独的x87协处理器芯片。
同时,英特尔通过引入不向后兼容的全新64位平台(“ Itanium”或“ IA-64”),试图摆脱向后兼容的局面(这使得现代x86 CPU的设计非常复杂和繁琐)。。毫不奇怪,市场确实对采用率感到犹豫,因为所有旧的“ x86”软件都无法在其上使用。
不久之后,AMD推出了它自己的64位扩展,这次是围绕“ x86”遗留物设计的,类似于386扩展286的方式,从客户的角度来看,它的工作性能要好得多(尽管这对于汇编程序员来说意味着一点点棘手的问题,例如mov eax,1将修改整个64位rax寄存器,自动将前32位清零,等等...其中一些古怪的“规则”使原始32位指令集的64位扩展成为可能,从长远来看,效果很好,即使在初次阅读时可能会感到有点乱。
因此,现代的“ x86” PC确实在单个芯片中包含三个主要的不同CPU,即过时的16位80286,非常老的32位“ 686”和当前的64位“ x86-64”变体。从汇编的角度来看,它们全部共享基本的指令和语法,因此,如果您完全学习“ 686”指令集,则x86-64源代码对您来说似乎是最熟悉的。只要您仅使用指令集的基本子集(例如“ 686”子集),您的二进制文件就可以在所有15至20台旧x86 PC上的特定OS上运行。
同时,AMD(以及Cyrix和其他试图在“ x86”世界中与Intel竞争的制造商)正在生产与Intel二进制兼容的CPU。有时,他们试图引入一些扩展,例如AMD的“ 3Dnow”,但是程序员很少使用它们,并且通常被抛弃。唯一的例外是当前的64位模式,该模式最终由AMD和Intel设计,最终不得不放弃并将其从AMD复制到他们的CPU中(因为缺少向后兼容性,因此市场上不接受其Itanium IA-64) )。
但是,如果您像游戏开发者一样,追求最高性能,则必须在运行时检查CPU模型/功能,并根据指令扩展的可用性,可以为特定CPU提供不同的二进制变体,例如SSE2指令最多可以在所有64位CPU上运行,也可以使用新的扩展(例如SSE4和AVX512)在其他变体中运行,这些扩展仅适用于拥有最新CPU的少数消费者。
许多C编译器默认在x86上以32位或64位模式为目标(主要取决于OS或您的项目设置),并且仅使用非常有限的指令集(例如仅“ 686”)或不带SSE1 / 2的x86_64,因此它们的二进制文件可以在任何普通的x86 PC上运行。尽管代码不是最理想的,但并未使用当前CPU的现代功能。
这也是您要学习的第一件事,如果您以“ x86”汇编知识为目标,请从基本的指令集开始(例如仅80386),学习相关的原理,然后再看看如何扩展(跳过16位80286,即只是无用的折磨,更容易学习32位模式,然后,如果您真的很好奇,可以尝试看看16位模式有何不同,使用32位模式会使它变得更容易一些,但毫无意义。
64位比32位模式要复杂一些,但还算不错,如果您愿意,您甚至可以从那里开始(没有16位的那么棘手)。
顺便说一句,您仍然应该从C-lib开始进行I / O并赢得API调用,即您将需要C编译器。从纯ASM访问Win API有点棘手(如果只是学习ASM基础知识,这是不必要的干扰),并且您不能在现代OS下直接访问硬件,因此,没有OS API服务就无法进行I / O,与旧的16位模式不同,该模式没有保护,您可以自由访问硬件外围设备。您可以从C包装器调用asm函数,然后可以处理I / O,内存管理和其他与OS API相关的事情,着重于asm函数中的纯算法和asm编程(这也是在实际项目中使用汇编的方式) ,您今天不会在asm中编写整个应用,而是将其保存在C / C ++中,并且仅重写ASM中性能最关键的部分,
顺便说一句,C是可移植的语言。如果仅使用标准C库,它将在几乎所有东西上都可以工作(SOURCE),但是只有在将其编译为特定目标平台BINARY之后,才可以使用。
Windows是一个很小的世界,主要限于x86(IA-64和DEC Alpha有Windows变体,它们无法运行x86二进制文件,但是它们是专门工作的专业机器,鲜为人知。因此,如果您使用默认选项编译Win32 x86可执行文件,则in将仅使用x86指令的有限子集在99%的Windows计算机上(次优)运行。如果您打开编译器以使用CPU的某些现代功能,则如果John Doe的x86 CPU不支持您的机器代码使用的功能之一,则生成的二进制文件可能无法在John Doe的PC上运行。
对于许多应用程序而言,此默认子集已绰绰有余,并且您无需理会扩展说明。只有极少数的应用程序需要最高的性能,例如游戏,CFD或其他科学计算……您可以安全地忽略运行性能+ 5-10%的普通网络浏览器(并且驱动程序/某处通常会有一些经过微调的功能)当前CPU的代码(确实可以处理主要的性能问题),例如解码视频/等,因此,即使是使用通用“ x86”目标编译的网络浏览器也将从中受益。
您不需要产生9999个asm代码变体,如果只想使其运行,基本上只需要32或64位变体(取决于目标,我对Windows世界并不了解,但是使用现代OS您可以使用基本的x86指令安全地仅定位到64位(即90%以上的用户),这将在“无处不在”(在x86窗口上)工作。
但是使用这样的asm并没有多大意义(出于教育目的,完全从此开始是非常有意义的),因为这样的asm的性能将不是最佳的,因此您已经可以使用C或C ++来获得类似的结果结果。对于有意义的用法,您还必须学习现代的扩展功能,在运行时检查可用功能,并使用针对特定CPU类型的最佳机器代码动态加载功能的正确变体。这就是您可能需要编写9999个具有相同功能的变体的部分(还不错,通常4-7个变体可能会覆盖大多数可用的CPU,一个兼容性回退仅使用基本指令集,然后再少一些专门的语言,例如SSE3 ,+ SSE4,AVX1 / 2,AVX512等)。同样,在现实世界中需要最高性能的软件非常罕见,即使是大多数较简单的游戏也都可以使用次优的二进制代码来完全正常工作,只有当您正在像Unreal-engine开发人员这样的尖端技术上工作时,您才需要关心那些精细的调整。大多数时候,收益不值得投资。
毕竟,现在有大量使用SW的软件,它们是用C#,Java甚至JavaScript编写的(或者PHP ...我真的提到了吗?现在感觉很脏),显然性能不是问题,否则大多数一段时间后将其重写为C ++,以提高性能。
您一直声称用 C 编写的程序可以被其他人在他们的计算机上使用而不会出现问题。仅当他们在 SPARC 工作站上运行 Windows,而不是 Linux、OS X、OpenBSD 或 Solaris 时,情况才是如此……
\n\n在其他操作系统上显示窗口或执行网络 I/O(“ping google.com”)时必须使用的 C 库是不同的。尤其ping是这是一个非常不可移植的事情,因为发送 ICMP 回显请求数据包通常是特权操作。(例如,pingLinux 上的可执行文件是 setuid-root,因此它可以强制执行速率限制。)您可能会system("ping google.com")在自己的程序中使用网络套接字而不是使用网络套接字。更好的例子是发出 HTTP 请求,因为任何进程都可以打开 TCP 连接。
您可以编写可移植的 C 程序,但您绝对必须努力使它们可移植。人们需要源代码,以便他们可以为自己的系统编译它。(这就是为什么 Unix 有以源代码形式分发软件的传统:每个人都需要使用自己版本的库为自己的系统编译它。)
\n\n编译的二进制文件只能在其编译的目标平台上运行,例如 x86-64 Windows。这样的二进制文件无法在 ARM Windows 或仅 32 位的 x86 Windows 上运行。
\n\n当用 asm 编写时,您的源代码特定于目标平台(包括 CPU,而不仅仅是可用的系统调用和库)。因此,您将为 32 位 x86 Windows 编写代码,而不仅仅是“为 Windows”编写代码,并且 C 编译器能够从同一源生成 x86 32 位或 x86-64 64 位二进制文件。
\n\n如果您首先关心的只是 32 位 x86 Windows,那么区别并不大。(每台“普通”Windows 计算机都可以运行 32 位 x86 二进制文件,因此如果您想移植到其他 Windows 计算机,您应该这样做。)
\n\n例如,让我们使用Godbolt 编译器资源管理器(source+asm)来查看一个简单的 C 程序如何编译为两个不同 x86 平台的不同 asm:
\n\n#include <stdio.h>\nint main() { puts("Hello World"); }\nRun Code Online (Sandbox Code Playgroud)\n\n编译为 x86-64 Linux 的 asm(x86-64 System V 调用约定),使用gcc -O3(Intel 语法模式而不是 AT&T,Godbolt 通过它-masm=intel):
.LC0:\n .string "Hello World"\nmain:\n sub rsp, 8 # align the stack before a call\n mov edi, OFFSET FLAT:.LC0\n call puts # first arg passed in RDI\n xor eax, eax # eax = 0 = return value.\n add rsp, 8 # restore the stack\n ret\nRun Code Online (Sandbox Code Playgroud)\n\n但是适用于 32 位 x86 Windows 的 MSVC 将其编译为以下 asm:
\n\n$SG5328 DB \'Hello World\', 00H\nEXTRN _puts:PROC\n_main PROC\n push OFFSET $SG5328\n call _puts ; first arg passed on the stack\n add esp, 4 ; clean up args\n xor eax, eax ; eax = 0 = return value.\n ret 0\n_main ENDP\nRun Code Online (Sandbox Code Playgroud)\n\n汇编器语法不同(MASM 与 GAS),但这不是重要的事情。重要的是,指向字符串文字的指针是在堆栈上传递的(使用push)而不是在寄存器中传递。
在 Windows 上, asm 符号名称puts带有前缀_,但在 Linux 上则没有。
由于这是 32 位代码,因此堆栈槽的宽度只有 4 个字节,而不是 8 个字节。
\n\n这适用于puts在两个平台上都可用的 ISO C 函数。在 Windows 上,您可以调用MessageBoxAWinAPI DLL,但 Linux 没有“本机”图形 API。大多数桌面都需要 X11 库,但并不是每个人都使用 X11。
由于您已经了解 C,因此查看编译器输出是开始学习汇编的好方法。 请参阅 Matt Godbolt 的 CppCon2017 演讲\xe2\x80\x9c 我的编译器最近为我做了什么?打开编译器的盖子 xe2x80x9d。(还有关于编写编译为有趣的 asm 的微小函数的进一步建议:How to remove "noise" from GCC/clang assembly output?)。
\n汇编代码不能在 CPU 上运行。它需要通过称为汇编程序的程序转换为机器代码。这种转换通常很简单(通常,汇编程序将包含汇编代码的文本转换为某些目标代码;然后链接器将多个目标文件聚合为可执行文件 - 并解析重定位;该可执行文件包含机器代码)。
x86 有多种变体(例如,某些处理器(但不是全部)接受AVX等扩展,并且有 32 位和 64 位之分)。
最后,Windows 的用户态 x86 程序是一些可执行文件,它无法在运行其他操作系统(例如 Linux)的相同 x86 架构(甚至相同硬件)上运行,因为 ABI 、系统调用、可执行格式(Windows 上的PE ,Linux 上的ELF)不同。阅读操作系统:三篇简单文章,了解有关操作系统的更多信息。
| 归档时间: |
|
| 查看次数: |
502 次 |
| 最近记录: |