汇编,机器代码,字节码和操作码之间的实际关系是什么?

汇编,机器代码,字节码和操作码之间的实际关系是什么?

我已经阅读了大部分有关汇编和机器代码的SO问题,例如,但它们的级别太高,并没有显示实际汇编代码转换为机器代码的示例。 结果,我仍然不明白它是如何在更深层次上运作的。

这个问题的理想答案将显示一些汇编代码的具体示例,例如下面的代码段,以及每个汇编指令如何映射到机器代码,字节码和/或操作码。 这样的答案对未来的人们学习集会非常有帮助,因为在过去几天的挖掘过程中,我还没有找到任何明确的总结。

我要找的主要内容是:

  1. 一段汇编代码
  2. 一段机器代码
  3. 程序集和机器代码片段之间的映射 (如何进行映射,或者至少是一些一般示例,以及如何知道如何执行此操作,Web上的所有这些信息都在哪里)
  4. 如何解释机器代码 (就像操作码以某种方式相关,以及网上关于所有这些数字意味着什么的所有信息)

注意:我没有计算机科学背景,所以我在过去几年里一直在慢慢走低水平,现在已经到了想要了解assembly和机器代码的程度。

assembly与机器代码之间的关系

我目前的理解是“汇编程序”(如NASM)接收汇编代码并从中创建机器代码。

所以当你编译一些像这个example.asm这样的程序集时:

 global main section .text main: call write write: mov rax, 0x2000004 mov rdi, 1 mov rsi, message mov rdx, length syscall section .data message: db 'Hello, world!', 0xa length: equ $ - message 

(用nasm -f macho64 -o example.o example.asm编译它)。 它输出这个example.o对象文件:

 cffa edfe 0700 0001 0300 0000 0100 0000 0200 0000 0001 0000 0000 0000 0000 0000 1900 0000 e800 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 2e00 0000 0000 0000 2001 0000 0000 0000 2e00 0000 0000 0000 0700 0000 0700 0000 0200 0000 0000 0000 5f5f 7465 7874 0000 0000 0000 0000 0000 5f5f 5445 5854 0000 0000 0000 0000 0000 0000 0000 0000 0000 2000 0000 0000 0000 2001 0000 0000 0000 5001 0000 0100 0000 0005 0080 0000 0000 0000 0000 0000 0000 5f5f 6461 7461 0000 0000 0000 0000 0000 5f5f 4441 5441 0000 0000 0000 0000 0000 2000 0000 0000 0000 0e00 0000 0000 0000 4001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0200 0000 1800 0000 5801 0000 0400 0000 9801 0000 1c00 0000 e800 0000 00b8 0400 0002 bf01 0000 0048 be00 0000 0000 0000 00ba 0e00 0000 0f05 4865 6c6c 6f2c 2077 6f72 6c64 210a 0000 1100 0000 0100 000e 0700 0000 0e01 0000 0500 0000 0000 0000 0d00 0000 0e02 0000 2000 0000 0000 0000 1500 0000 0200 0000 0e00 0000 0000 0000 0100 0000 0f01 0000 0000 0000 0000 0000 0073 7461 7274 0077 7269 7465 006d 6573 7361 6765 006c 656e 6774 6800 

(这是example.o的全部内容)。 当你使用ld -o example example.o “链接”它时,它会为你提供更多的机器代码:

 cffa edfe 0700 0001 0300 0080 0200 0000 0d00 0000 7803 0000 8500 0000 0000 0000 1900 0000 4800 0000 5f5f 5041 4745 5a45 524f 0000 0000 0000 0000 0000 0000 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1900 0000 9800 0000 5f5f 5445 5854 0000 0000 0000 0000 0000 0010 0000 0000 0000 0010 0000 0000 0000 ... 523 lines of this 

但它是如何从assembly说明到这些数字的? 是否存在某种标准参考,列出所有这些数字及其含义,适用于您所使用的任何体系结构(我在OSX上使用x86-64到NASM),以及每组数字如何映射到每个汇编指令?

据我所知,每台机器的机器代码都不同,有几十种甚至数百种不同类型的机器。 所以我目前还在寻找如何将程序集转换为每个程序集(这将是复杂的)。 我只是对一个示例感兴趣,该示例说明了转换的工作原理,并且任何架构都可以作为示例。 从那时起,我可以去研究我感兴趣的特定架构并找到映射。

汇编和字节码之间的关系(或称为“操作码”?)

因此,从我到目前为止的阅读,如上所示,程序集将转换为机器代码。

但现在我感到困惑。 我看到人们谈论字节码,比如在这个SO答案中 ,显示这样的东西:

 void myfunc(int a) { printf("%s", a); } 

该函数的程序集如下所示:

 OP Params OpName Description 13 82 6a PushString 82 means string, 6a is the address of "%s" So this function pushes a pointer to "%s" on the stack. 13 83 00 PushInt 83 means integer, 00 means the one on the top of the stack. So this function gets the integer at the top of the stack, And pushes it on the stack again 17 13 88 Call 1388 is printf, so this calls the printf function 03 02 Pop This pops the two things we pushed back off the stack 02 Return This returns to the calling code. 

所以我感到困惑。 做一些挖掘,我不知道这些2位hex数字如13 82 6a中的每一个是否分别称为“操作码”,并且它们的整个集合被称为“字节码”作为一个包罗万象的术语。 另外,我找不到列出所有这些2位hex数的表,以及它们与机器代码或汇编的关系。

总而言之,我非常期待一个示例,展示汇编指令如何映射到机器代码,以及它与字节码和/或操作码的关系。 (我不是在寻找编译器如何做到这一点,只是一般的映射是如何工作的)。 我认为这不仅可以为我自己澄清这一点,也可以为那些有兴趣了解更多有关裸机的人提供更多信息。

知道这一点的另一个原因是,因此可以理解LLVM编译器如何生成机器代码。 他们是否有某种2位操作码或机器码4位序列的“完整列表”,并且确切知道如何映射到任何特定于架构的程序集? 他们从哪里获得这些信息? 对这个整体问题的回答将使LLVM如何实现其代码生成更加清晰。

更新

从@ HansPassant的评论更新。 我实际上并不关心单词之间的实际区别,对不起,如果不清楚的话。 我只想知道这一点:程序集如何映射到机器代码(以及在哪里开始查找在Web上保存该信息的引用),以及在该进程中的任何位置使用的操作码或字节码? 如果是这样怎么样?

是的,每个体系结构都有一个指令集引用,用于指示如何编码指令。 对于x86,它是英特尔®64和IA-32架构软件开发人员手册第2卷(2A,2B和2C):指令集参考,AZ

大多数汇编程序(包括nasm )都可以为您生成列表文件。 将示例代码提供给nasm -l ,我们得到:

  1 global main 2 section .text 3 4 main: 5 00000000 E800000000 call write 6 7 write: 8 00000005 B804000002 mov rax, 0x2000004 9 0000000A BF01000000 mov rdi, 1 10 0000000F 48BE- mov rsi, message 11 00000011 [0000000000000000] 12 00000019 BA0E000000 mov rdx, length 13 0000001E 0F05 syscall 14 15 section .data 16 00000000 48656C6C6F2C20776F- message: db 'Hello, world!', 0xa 17 00000009 726C64210A 18 length: equ $ - message 

您可以在第三列中看到生成的机器代码(第一个是行号,第二个是地址)。

请注意,汇编程序的输出是目标文件,链接器的输出是可执行文件。 这两者都具有复杂的结构,并且不仅包含机器代码。 这就是你的hexdump与上面列表不同的原因。

操作码通常被认为是指定要执行的操作的机器代码指令的一部分。 例如,在上面的代码中,您有B804000002 mov rax, 0x2000004B8是操作码, 04000002是直接操作数。

字节码通常不在汇编上下文中使用,它可以被认为是虚拟机的机器代码。


对于演练,x86是一个非常复杂的架构。 但是您的示例代码恰好有一个简单的指令,即syscall 。 那么让我们看看如何将其转换为机器代码。 打开上面提到的参考pdf,并转到第4章中有关syscall的部分。您将立即看到它列为操作码0F 05 。 由于它不需要任何操作数,我们完成了,这2个字节是机器代码。 我们怎么回头? 转到Appendix A: Opcode map 。 第A.1节告诉我们: For 2-byte opcodes beginning with 0FH (Table A-3), skip any instruction prefixes, the 0FH byte (0FH may be preceded by 66H, F2H, or F3H) and use the upper and lower 4-bit values of the next opcode byte to index table rows and columns. 。 好的,我们跳过0F并将05拆分为05并在第0行第5行的表A-3中查找。 我们发现这是一个syscall指令。

是否存在某种标准参考,列出所有这些数字及其含义,对于您所处的任何体系结构,以及每组数字如何映射到每个汇编指令?

是的,虽然它们可能非常复杂。 此外,由于汇编程序和编译器的普及,它们也很难找到,因为几乎没有人使用它们。

程序集与字节码之间的关系

  • 机器代码 – 读入CPU的一个或一系列值。 每个数字是“指令”或“操作码”,并且可以跟随一个或多个参数来作用。 在链接代码中, 13告诉处理器将字符串推入堆栈。
  • OpCode – 命令的值:在示例中,用于推送字符串的操作码是13
  • 汇编 – CPU内部机器代码的人类可读指令。 几乎总是每个机器代码指令一个汇编指令。 在我链接到的代码中,“汇编”指令PushString映射到机器指令13
  • 字节代码 – 由于每个处理器使用不同的机器代码,有时程序编译为假想的“虚拟机”的机器代码,然后有一个程序读取这个假的机器代码并执行它(通过仿真或JIT)。 Java和C#以及VB都这样做。 这种“假的”机器代码称为“字节代码”,尽管这些术语通常可互换使用。

我应该注意,本文和我链接到的其他post中使用的字节码指令是我在公司工作的专有字节代码的简化摘录。 我们有一个专有的编程语言编译成这个字节码,由我们的产品解释,我提到的一些值是我们实际使用的实际字节码。 13实际上是pushAnything具有复杂参数,但我保持简单的答案。

你已经清楚地完成了自己的一些功课,并且我说了很好的东西(并投了你一个)。

正如您所经历的那样,您阅读的越多,您说的就越多,“嗯?”

好的,首先,当你遇到“字节码”这个词时,只需关闭窗口并停止阅读,因为你走错了路; 可能是最好的切线,最糟糕的是,你可能正在阅读一个试图听起来比他真实更聪明的人通过在他的写作中抛出技术性的流行语。

现在,至于“操作码”这个​​词,是的,那些确实存在,但确实理解这些数字实际上是象征性的,供人类在概念上掌握。 在现实生活中,它们是超微型开关。

如果你真的喜欢历史和互联网之前的技术(或彩色电视)那么查找像蝴蝶开关,真空管,蝴蝶女孩这样的短语,我忘了其他的话。 这是在晶体管存在之前回来的。 最初的大型计算机实际上使用了真空管并产生了足够的热量来加热冬季死亡时办公楼的整个楼层(或两三个)。 电流消耗令人震惊。

关于这一切的事情要记住,那些计算机是通过单独翻转蝶形开关(“蝙蝠手柄”是有时使用的另一个术语)来“编程”的,它连接和断开各个管道的各个线路,我忘了还有什么。

事实是:您通过翻转连接到连接到各种管的线的蝙蝠手柄来编程计算机。

快进到今天……

当你写一个90h的操作码,(我认为这是x86中的NOP,有人纠正我,我会解决它)你正在做(今天的高科技wowee-zowee)和蝴蝶女孩做的一样电脑的石器时代。

具体来说,你正在“抛出”这些“蝴蝶开关”……

  • 7 – 开
  • 6 – 关
  • 5 – 关
  • 4 – 开
  • 3 – 关
  • 2 – 关闭
  • 1 – 关
  • 0 – 关

这是最大的不同(今天的高科技wowee-zowee的一部分)……

他们不得不将这些开关准确地扔在地板上的一个地方。 你会在任何你想要的地方翻转它们。 其他三个项目将合作并为您做出决定。

这三个程序是 – 汇编程序 – 链接程序 – 加载程序

那么(我希望)这有助于你理解OPCODE是一系列小开关的心理表征,这些开关将被“打开”或“关闭”。

(事实上​​,高科技wowee-zowee已经更进了一步,但它与之前的gnerations的蝴蝶开关效果相同。)

无论如何,它的工作原理如下。

人类决定不做任何事情的指示; 叫做NOP

所以,你在文本编辑器中输入字母NOP就像这样

  NOP ;This is a No operation instruction 

然后保存文件。

然后,您要求汇编程序汇编该文件

当汇编程序看到NOP他在Object文件中创建90 (hex),并为链接器创建。

链接器使用目标文件并创建可执行文件

Loader将可执行文件放在任何需要的位置。 (注意,在微型计算机的旧时代,软件编写者必须决定将可执行文件放在何处;这是你不相信的冲突诱饵。)

无论如何, NOPEXE文件的某个地方变成了90 ,并且装载程序根据你不必再担心的179条规则将它固定在一个很好的区域。

然后加载器离开图片并让程序拥有CPU。

CPU取出您的第一条指令并开始服从。

当CPU到达包含90的字节时,它将与过去几代的蝶形切换相同。

虽然电流不会在地板上传输一堆长线,但它将在ASIC内部进行高度相似(function相同)的事情。

现在有了所有的内容(感谢你还在阅读),你可以理解这简单解释了操作码实际上是什么……

操作码是古代蝴蝶开关的范例表示。

现在关于什么是机器代码的第二个问题。

机器代码是一堆操作码

如果其中任何一项不清楚,请在评论部分询问,我将尝试编辑此答案。

简述:

“汇编”是你通过“汇编程序”提供的。 汇编程序是一个程序,它读入几个穿孔卡片组并将它们“组装”成一个程序。

或者至少过去。 现在卡被磁盘文件替换。 但是“卡片”上的数据是“机器语言”,它是机器指令的数值。

但是现代汇编程序是SAP – 符号汇编程序 – 因此您可以用符号替换数值 – 例如对于加载指令使用“LOD”,对于寄存器1使用“R1”,对于指令地址26734使用“label5”。

“机器语言”是表示CPU的单个指令(或“命令”,如果您是英国人)的方式。 对于符号汇编程序,您可能有“LOD R1,LOOPCOUNT”来表示将标记为LOOPCOUNT的值加载到寄存器1中的指令。顺便说一下,“LOD”是“操作码” – (符号版本的))告诉计算机下一步做什么的数值。 (请注意,每种不同的计算机设计都使用不同的机器语言,操作码可能带有不同的符号。您在网上找到的大部分内容都是英特尔机器语言的一种或另一种版本,但您会发现, IBM 370完全不同。)

“字节码”是一种不同的“机器语言”,它在“虚拟机”而不是真实硬件上运行。 最着名的情况是Java虚拟机。 “字节码”是一种类似于常规“机器语言”的符号,但在某种程度上是理想化的,因为在虚拟机上运行可以将其从真实硬件环境的某些现实中解脱出来。

关系是:

 Assembler instruction (readable) -> machine code (binary) machine code = opcode + operands 

汇编程序指令是人类可读代码,例如: mov rax, 0x2000004

操作码是与指令相关的机器代码的一部分,但是从CPU的角度来看(所以它不仅仅是MOV,而是MOV常量来注册)。 例如,请参阅此处了解i386 MOV操作码:

  • MOV reg32, immediate value编码为B8 +寄存器代码(AX是第一个,所以它为0),
  • 操作码之后是操作数0x20000004,它以小端逻辑编码为: 04 00 00 02

字节码相当于机器代码,但适用于JVM等虚拟机。 术语字节码来自使用该技术的第一个环境(来自UCSD pascal编译器的p代码),它使用一个字节来编码虚拟指令。 例如,您可以在此处找到小型p代码insruction集,以及此处更新且更广泛的JVM字节代码

需要注意的是:LLVM使用以压缩forms存储的中间格式(IF),也称为字节码。 这允许在生成本机代码之前执行机器中立代码分析优化

汇编:汇编器+数据字节+运算符的人类可读教师

机器代码:CPU理解的实际位序列。

它包含:

  • 操作码,
  • 注册使用,
  • 偏离PC寄存器,
  • 和类似的信息

字节码:这是解释器读取的代码(java的大多数实现实际上是一个解释字节码并使用该字节码来选择机器代码序列以使CPU实际执行的解释器)。 字节码通常用于使相同的源代码在几个不同的CPU上工作。

操作码:机器码的前一个(或两个)字节。 它就像一个选择器,告诉CPU它要执行的CPU哪个微代码序列(类似于C中的switch语句)

微代码:CPU中用于执行机器代码的硬连线指令序列。
有许多微码序列,每个操作码至少有一个序列。 通常,机器代码的其余部分只是由操作码选择的微码序列的参数,每个微码序列包含打开/关闭门,时钟数据,向/从累加器传递信息等的指令。