如何在现代使用汇编(例如使用C / C ++)?

我理解计算机如何处理基本原理,例如,程序可以用C#,C等“高级”语言编写,然后将其分解为目标代码,然后分解为处理器理解的二进制代码。 但是,我真的想学习assembly,以及它如何在现代应用中使用。

我知道处理器在基本x86指令集之上有不同的指令集。 所有汇编语言都支持所有指令集吗?

有多少汇编语言? 有多少与其他语言一起使用?

如何在程序集中编写例程,然后将其编译为对象/二进制代码?

那么有人会如何用C或C ++这样的语言引用汇编代码中的函数/例程?

我们怎么知道我们在汇编中编写的代码是最快的?

是否有关于汇编语言的推荐书籍/将它们与现代程序一起使用?

对于问题的数量感到抱歉,我希望它们足够通用,对其他人有用,并且足够简单,让其他人回答!

但是,我真的想学习assembly,以及它如何在现代应用中使用。

在“普通”PC上,它仅用于时间关键处理,我认为实时多媒体处理仍然可以从手工组装中获益。 在嵌入式系统上,马力较少,可能会有更多的使用领域。

但是,请记住,它不仅仅是“嘿,这段代码很慢,我会在汇编中重写它,它会通过魔法来快速进行”:必须仔细编写汇编,写出知道什么是快速的,它是什么慢在您的特定架构上,并牢记现代处理器的所有复杂性(分支错误预测,乱序执行,……)。 通常,由初学者到中型程序集编程器编写的程序集将比由优秀的现代优化编译器生成的最终机器代码 。 x86上的性能通常非常复杂,应该留给那些知道他们做什么的人=>并且大多数都是编译器编写者。 :)例如,看看这个 。

我知道处理器在基本x86指令集之上有不同的指令集。 所有汇编语言都支持所有指令集吗?

我觉得你在这里混淆了一些事情。 许多(=所有现代) x86处理器都支持在定义原始x86指令集之后引入的附加指令和指令集。 实际上,现在几乎所有的x86软件都被编译为利用后Pentiumfunction; 您可以使用CPUID指令查询处理器以查看它是否支持某些function。 显然,如果你想使用助记符来进行一些较新的指令集指令,你的汇编程序(即在实际机器代码中翻译助记符的软件)必须知道它们。

相反,如果您正在讨论其他系列处理器的其他(非x86)指令集,那么每个汇编器应该支持目标处理器可以运行的指令。 并非所有汇编语言的指令都可以直接替换其他语言,并且通常将汇编代码从架构移植到另一个架构代码通常是一项艰巨而艰巨的工作。

有多少汇编语言?

从理论上讲,每个处理器系列至少有一种方言。 请记住,同一汇编语言也有不同的表示法; 例如,以下两条指令是用AT&T和Intel表示法编写的相同x86内容:

 mov $4, %eax // AT&T notation mov eax, 4 // Intel notation 

如何在程序集中编写例程,然后将其编译为对象/二进制代码?

如果要在使用其他语言编写的应用程序中嵌入例程,则应使用该语言提供的工具,在C / C ++中使用asm块。

相反,如果你想在汇编中编写一个完整的应用程序,那么你必须按照你想要使用的汇编程序的语法规则来编写汇编。

我们怎么知道我们在汇编中编写的代码是最快的?

从理论上讲,因为它是最接近裸机的,所以你可以使机器完全按照你想要的方式运行,而不需要编译器考虑到在某些特定情况下无关紧要的语言function。 在实践中,由于机器通常比汇编语言暴露的复杂得多,正如我所说的那样,汇编语言通常比编译器生成的机器代码慢,这考虑了普通程序员不知道的许多微妙之处。


附录

我忘记了:知道阅读汇编,至少一点点,在调试优化器坏了/只在发布版本中出现的奇怪问题时非常有用/你必须处理heisenbugs /当源 -级别调试不可用或其他类似的东西; 看看这里的评论。

英特尔和x86在反向兼容性方面做得很好,这肯定帮助了他们,但同时又大大受伤。 8088/8086到286到386,到486,pentium,pentium pro等的内部到现在每次都有一些重新设计。 早期为操作系统添加保护机制,以保护应用程序彼此之间以及内核,然后通过添加执行单元,超标量以及随附的所有内容,多核处理器等来实现性能。过去是真正的单一AX原始处理器中的寄存器变成谁知道现代处理器中有多少不同的东西。 最初你的程序是按照所写的顺序执行的,今天它被切割并切片并且以这样的方式执行,使得所呈现的指令的意图得到尊重,但执行可能是乱序的并且是并行的。 在表面上隐藏的许多新技巧似乎是一个非常古老的指令集。

指令集从8/16位根变为32位,变为64位,因此汇编语言也必须改变。 例如,将EAX添加到AX,AH和AL。 偶尔会添加其他说明。 但原始的加载,存储,添加,减去和/或等指令都在那里。 我很长时间没有完成x86,并且看到语法已经改变和/或特定的汇编程序搞砸了x86语法而感到震惊。 有很多工具,所以如果一个与你正在使用的书籍或网页不匹配,那里就有一个。

因此,考虑到这个系列的汇编语言是对是错,汇编语言可能已经改变了语法并且不一定是反向兼容的,但是指令集或机器语言或其他类似术语(汇编代表的操作码/位)会说现代x86处理器仍然支持大部分原始指令集。 286特定的细微差别可能不会像特定代的其他新function那样起作用,但核心指令,加载,存储,添加,减去,推送,弹出等都仍然有效并将继续工作。 我觉得“沿着车道的中心行驶”更好,不要进入芯片或工具特定的酥油机function,使用基本的无聊,从语言的时间语法开始就一直工作。

因为家族中的每一代都在尝试某些function,通常是性能,各个指令分发给各个执行单元的方式发生变化……每一代…为了手动调整汇编程序的性能,试图将 – 编译器,充其量是困难的。 您需要有关正在调整的特定处理器的详细信息。 从早期的x86天到现在,不幸的是,使代码在一个芯片上执行得更快的原因往往会导致下一代运行速度变慢。 也许这是伪装的营销工具,不确定,“购买热门的新处理器,其成本是现在的两倍,宣传时钟速度的两倍,但运行相同的Windows副本的速度要慢30%。在编译下一个版本的Windows时(这个芯片已经过时),它的性能会翻倍“。 这样做的另一个副作用是,此时您不能使用一个C程序并创建一个在所有x86处理器上快速运行的二进制文件,以获得调整特定处理器所需的性能,这意味着您至少需要告诉编译器优化和优化家庭。 就像窗户或办公室,或者你作为二进制文件分发的东西,你可能不会或不想以某种方式将同一程序的几个不同调整的副本埋在一个包或一个二进制文件中…沿着道路中心行驶。

作为所有硬件改进的结果,最好不要尝试将编译器输出或手动汇编器调整到任何一个芯片。 平均而言,硬件改进将弥补编译器调优的不足,同样的程序希望每一代运行速度更快一些。 其中一家芯片供应商过去的目标是使今天流行的编译二进制文件明天运行得更快,另一家供应商改进了内部组件,如果你重新编译今天新内部的源代码,你明天就可以运行得更快。 供应商之间的那些活动并不一定会持续下去,每一代今天运行的二进制文件都比较慢,但明天重新编译源代码的速度相同或更慢。 它将运行明天重写程序更快,有时与相同的编译器有时你需要明天编译器。 这不好玩!

那么我们如何才能知道特定的编译或手工编译程序是否尽可能快? 我们不,实际上对于x86你可以保证它不是,在家庭中的一个芯片上运行它很慢,在另一个芯片上运行它可能是快速的。 x86与否,除了你在微控制器上找到的非常短的程序或非常确定的程序之外,你不能说这是最快的解决方案。 例如,高速缓存即使可以调整也很难,以及它背后的内存,特别是在PC上,用户可以选择各种大小,速度,等级,库等,并调整BIOS设置以更改更多设置,真的不能告诉编译器调整它。 因此,即使在同一台计算机上,同一个处理器使用相同的编译二进制文件,您也可以转动某些旋钮,使该程序运行得更快或更慢。 改变处理器系列,改变芯片组,主板等。没有可能的方法来调整这么多变量。 x86 pc业务的性质变得太混乱了。

其他芯片系列几乎没有问题。 有些可能但不是全部。 所以这些不是一般性陈述,而是特定于x86芯片系列。 x86系列不是例外。 可能是您想要学习的最后一个汇编器/指令集。

关于这个主题有大量的网站和书籍,不能说一个比另一个好。 我从英特尔的原始8088/86书籍中学到了386和486本书,之后没有找到英特尔书籍(或其他任何嘘声)。 您将需要一个指令集引用,以及一个像nasm或gas这样的汇编程序(gnu汇编程序,是大多数基于gcc的编译器工具链附带的binutils的一部分)。 至于C到/来自汇编程序接口你可以通过实验没有别的想法,编写一个带有一些小C函数的小C程序,反汇编或编译到汇编程序,并查看什么寄存器和/或堆栈如何用于在函数之间传递参数。 保持您的function简单,只使用几个参数,您的汇编程序可能会正常工作。 如果没有查看调用代码的函数的汇编程序并找出参数的位置。 这些都记录在某个地方,这些日子可能比旧的好得多。 在8088/86早期,您拥有小型,小型,中型,大型和大型编译器模型,调用约定可能各不相同。 除了下一个编译器之外,watcom(以前的zortech和其他名字)通过寄存器传递,borland和microsoft在堆栈上传递,如果不相同则非常接近。 现在有32位和64位平面存储空间和标准,你可以使用一个模型,而不必记住所有的细微差别(只有一组细微差别)。 内联汇编是一个选项,但从C编译器到C编译器各不相同,让它正常有效地工作比在自己的文件中编写汇编器更困难。 gcc和其他编译器可能允许你将汇编程序文件放在C编译器命令行上,就像它只是另一个C文件一样,它会找出你给它的内容并将它传递给汇编程序。 也就是说,如果您不想自己调用汇编程序并将对象放在C编译器命令行上。

如果没有别的东西反汇编很多简单的函数,添加一些参数并返回它们等。更改编译器优化设置,看看它如何改变所使用的指令,通常是戏剧性的。 从调试和性能的角度来看,即使你无法从头开始编写汇编程序也能读取它是非常有价值的。

并非所有处理器的编译器都是好的。 例如Gcc是一种适合所有人的尺寸,就像一个袜子或球帽,一个尺寸并不适合任何人。 对大多数目标都有好处但不是很好。 所以很可能比使用手动调整汇编程序的编译器做得更好,但平均来说,很多代码都不会赢。 这适用于大多数处理器,它们更具确定性,而不仅仅是x86系列。 它不是关于更少的指令,更少的指令不一定等于更快,甚至比普通的编译器更胜一筹,你必须了解缓存,获取,解码,执行状态机,存储器接口,存储器本身等。编译器优化关闭它很容易生成比编译器更快的代码,所以你应该只使用优化器,但也要明白这会增加编译器犯错误的风险。 您需要非常了解该工具,这可以追溯到经常拆解,以了解您的C代码和您今天使用的编译器如何相互交互。 没有编译器是完全符合标准的,因为标准本身是模糊的,使得语言的某些function完全由编译器决定(在中间行驶并且不要使用该语言的那些部分)。

从问题的本质来看,我建议用一些小函数编写一堆小函数或程序,编译成汇编程序或编译成一个对象并反汇编以查看编译器的作用。 务必在每个程序上使用不同的优化设置。 获得有关指令集的工作阅读知识(授予编译器或反汇编程序的asm输出,有很多额外的漏洞妨碍可读性,你必须看过去,如果你想要你几乎不需要它写汇编程序)。 如果这是您的目标,那么在您可以期望定期超越编译器之前,请自己进行5到20年的学习和实验。 到那时你将了解到,特别是对于这个芯片系列来说,这是徒劳的努力,你赢得了一些但是大部分都输了……将相同的代码编译(汇编)到其他芯片系列如arm会对你有利和mips一样,对C代码编译得很好,以及C代码编写得不好,并使C编程更好,而不是试图使汇编程序更好。 还可以尝试其他编译器,如llvm。 Gcc有许多怪癖,很多人认为是C语言标准,但它们是特定编译器的细微差别或问题。 能够阅读和分析编译器的汇编输出及其选项将提供这些知识。 所以我建议你学习指令集的阅读知识,而不必学习从头开始编写。

您需要从硬件的角度来看待它,汇编语言是根据CPU可以执行的操作创建的。 每次创建CPU中的新function时,都会创建适当的汇编指令,以便可以使用它。

因此,汇编非常依赖于CPU,C ++等高级语言提供了抽象,使我们不必考虑CPU指令等细节以及编译器生成优化的汇编代码。

编辑:

有多少汇编语言? 有多少与其他语言一起使用?

尽可能多的CPU有不同类型。 第二个问题我不明白。 程序集本身不与任何其他语言交互,输出,机器代码是。

如何在程序集中编写例程,然后将其编译为对象/二进制代码?`

原理类似于使用任何其他编译语言编写,使用汇编指令创建文本文件,使用汇编程序将其编译为机器代码。 然后将其与最终的运行时库链接。

那么有人会如何用C或C ++这样的语言引用汇编代码中的函数/例程?

C ++和C提供内联汇编,因此不需要链接,但是如果要链接,则需要按照与宿主语言相同/类似的调用约定创建汇编对象。 例如,某些语言在调用函数时会按特定顺序将参数推送到堆栈上的函数,因此您必须执行相同的操作。

我们怎么知道我们在汇编中编写的代码是最快的?

因为它最接近实际的硬件。 当您处理更高级别的语言时,您不知道编译器将对您的for循环执行什么操作。 然而,通常情况下,他们在优化代码方面做得比人类做得好(当然在非常特殊的情况下,你可能会得到更好的结果)。

那里有许多不同的汇编语言。 通常每个处理器指令集至少有一个,这意味着每个处理器类型都有一个。 您还应该记住的一件事是,即使对于单个处理器,也可能存在几种不同的汇编程序,这些汇编程序可能使用不同的语法,从正式视图构成不同的语言。 (对于x86,有masm,nasm,yasm,AT&T(默认使用GNU汇编程序的* nix汇编程序),可能还有更多)

对于x86,有许多不同的指令集,因为多年来架构发生了很多变化。 其中一些更改可能主要被视为附加指令,因此它们是前一个程序集的超级集合。 其他更改实际上可能会删除指令(x86没有人想到,但我听说过其他处理器上的一些)。 其他变化为处理器增加了操作模式,使事情变得更加复杂。

还有其他处理器具有完全不同的指令。

要学习汇编,您需要先选择要使用的目标处理器和汇编器。 我将假设您将使用x86,因此您需要决定是否要从16位分段,32位或64位开始。 许多书籍和在线教程都是你编写DOS程序的16位路径。 如果您想要在汇编中编写C程序的一部分,那么您可能希望使用32位或64位路由。

我所做的大多数汇编编程都是在C中内联,以优化某些东西,使用编译器不知道的指令,或者当我需要控制所使用的指令时。 在汇编中编写大量代码很困难,所以我让C编译器完成大部分工作。

有许多地方仍然由人们编写集会。 这在嵌入式引导加载程序(bios,u-boot,…)和操作系统代码中尤为常见,尽管这些代码中的许多开发人员从不直接编写任何程序集。 此代码可能是启动代码,必须在堆栈指针设置为可用值之前运行(或RAM由于某些其他原因尚未使用),因为它们需要适合小空间,和/或因为它们需要以C或其他更高级语言不直接支持的方式与硬件通信。 在OS中使用程序集的其他地方是写锁(自旋锁,关键部分,互斥锁和信号量)和上下文切换(从一个执行线程切换到另一个执行线程)。

通常编写程序集的其他地方是在某些库代码的实现中。 像strcpy这样的函数通常在汇编中针对不同的体系结构实现,因为通常有几种方法可以使用特定于处理器的操作来优化它们,而C实现可能使用更通用的循环。 这些function也经常被重复使用,从长远来看,手工优化这些function通常是值得的。

编写大量汇编的另一个相关的地方是编译器。 编译器必须知道如何实现事物并且其中许多产生汇编,因此它们具有内置于其中的汇编模板(或类似的东西)以用于生成输出代码。

即使您从未编写任何程序集,因为您知道目标系统的指令和寄存器通常很有用。 它们可以帮助调试,但它们也可以帮助编写代码。 了解目标处理器可以帮助您为其编写更好(更小和/或更快)的代码(即使是更高级别的语言),熟悉一些不同的处理器将帮助您编写对许多处理器有益的代码,因为你会知道CPU是如何工作的。

我们在实时工作中做了相当多的工作(比我们应该做的更多)。 当您与硬件通信并且需要执行特定的机器指令时,一点点组装也非常有用(例如:所有写入必须是16位写入,或者您将软管附近的寄存器)。

我今天倾向于看到的是更高级语言代码中的程序集插入。 这是如何完成取决于您的语言,有时编译器。

我知道处理器在基本x86指令集之上有不同的指令集。 所有汇编语言都支持所有指令集吗?

“汇编语言”是一种用词不当,至少在你使用它的方式。 汇编程序不是一种语言(CSgradle生可能会反对)和更多的转换器工具,它采用文本表示并从中生成二进制图像,文本元素(memnonics,标签和数字)与二进制文件之间的关系接近1:1元素。 汇编语言的元素背后没有更深层次的逻辑,因为它们被引用和重定向的可能性主要在第1级结束; 例如,你可以一次只在一条指令中使用EAX – 在下一条指令中下一次使用EAX与之前的使用没有关系,除了程序员想到的未写入的逻辑连接外 – 这就是为什么在汇编程序中创建错误非常容易。

如何在程序集中编写例程,然后将其编译为对象/二进制代码?

人们需要确定指令集的最低公分母,并将函数编码为代码要运行的预期体系结构的时间。 如果您没有编写在编写本文时定义的某个硬件平台(例如游戏机,嵌入式主板),则不再使用此function。

那么有人会如何用C或C ++这样的语言引用汇编代码中的函数/例程?

您需要在HLL中声明它们 – 请参阅编译器手册。

我们怎么知道我们在汇编中编写的代码是最快的?

没有办法知道。 对此感到高兴并编写代码。