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

Lan*_*ard 10 c compiler-construction assembly bytecode llvm

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

我看过最大约装配和机器代码的SO问题,比如这个,但他们太高水平和不显示的实际汇编代码示例被改造成机器代码.结果,我仍然不明白它是如何在更深层次上运作的.

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

我要找的主要内容是:

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

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

装配与机器代码之间的关系

我目前的理解是"汇编程序"(如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
Run Code Online (Sandbox Code Playgroud)

(用它编译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 
Run Code Online (Sandbox Code Playgroud)

(这是整个内容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
Run Code Online (Sandbox Code Playgroud)

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

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

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

因此,从我的阅读到目前为止,汇编被转换为机器代码,如上所示.

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

void myfunc(int a) {
  printf("%s", a);
}
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)

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

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

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

更新

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

Jes*_*ter 8

是的,每个体系结构都有一个指令集引用,用于指示如何编码指令.对于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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


对于演练,x86是一个非常复杂的架构.但是你的示例代码碰巧有一个简单的指令,syscall.那么让我们看看如何将其转换为机器代码.打开上面提到的参考pdf,然后转到syscall第4章中的部分.您将立即看到它列为操作码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成了0,5A-3在第0行第5列的表中查找.我们发现这是一个syscall指令.

  • 你必须阅读pdf参考.如果您要从汇编代码转到机器代码,只需在文档中查找相应的指令即可.对于这个例子,它说'B8 + rd id MOV r32,imm32`.要了解格式,您必须阅读"第2章指令格式". (2认同)

Moo*_*uck 7

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

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

汇编与字节码之间的关系

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

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


Use*_*r.1 7

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

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

好的,首先,当你遇到单词"bytecode"时,只需关闭窗口并停止阅读,因为你走错了路; 可能是最好的切线,在最坏的情况下,你可能正在阅读一个试图听起来比他真实更聪明的人,将技术性的流行语写入他的写作中.

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

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

关于这一切的问题是,那些计算机是通过单独翻转蝶形开关("蝙蝠手柄"是有时使用的另一个术语)进行"编程"的,它将各个管道中的各个线连接起来并断开,我忘记了其他什么.

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

快进到今天......

当你写一个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
Run Code Online (Sandbox Code Playgroud)

然后保存文件.

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

当汇编程序看到NOP90Object文件中创建(以十六进制)时,他正在为链接器创建.

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

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

无论如何,它NOP变成90EXE文件中的某个位置,并且加载器将它放在一个适合您的区域,基于179条规则,您不必再担心了.

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

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

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

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

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

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

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

机器代码是一堆操作码

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


Chr*_*phe 6

关系是:

Assembler instruction (readable) ->  machine code (binary) 

machine code = opcode + operands
Run Code Online (Sandbox Code Playgroud)

所述汇编程序指令是人类可读代码,诸如: 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使用以压缩形式存储的中间格式(IF),也称为字节码.这允许在生成本机代码之前执行机器中立代码分析优化


Hot*_*cks 6

简述:

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

或者至少过去曾经如此.现在卡被磁盘文件替换.但是"卡片"上的数据是"机器语言",它是机器指令的数值.

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

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

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