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

时间:2014-12-23 23:15:49

标签: c assembly compiler-construction llvm bytecode

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

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

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

我正在寻找的主要内容是:

  1. 汇编代码片段
  2. 一段机器代码
  3. 汇编和机器代码片段之间的映射(如何进行映射,或者至少是一些一般示例,以及如何知道如何执行此操作,网上所有这些信息都在哪里)
  4. 如何解释机器代码(就像操作码以某种方式相关,以及网络上关于所有这些数字意味着的所有信息在哪里)
  5. 注意:我没有计算机科学背景,所以我在过去几年里一直在慢慢走低水平,现在已经到了想要了解装配和机器代码的程度。

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

    我目前的理解是“汇编程序”(例如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
    

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

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

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

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

    但现在我感到困惑。我看到人们谈论字节码,例如in this SO answer,显示如下内容:

    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位十六进制数字(如13 82 6a)是否各自被称为“操作码”,并且它们的整个集合被称为“字节码”作为一个全部术语。另外,我找不到列出所有这些2位十六进制数的表,以及它们与机器代码或汇编的关系。

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

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

    更新

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

6 个答案:

答案 0 :(得分:8)

是的,每个架构都有一个指令集引用,用于指示如何编码指令。对于x86,它是Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z

大多数汇编程序(包括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是直接操作数。

Bytecode通常不在汇编语境中使用,它可以被认为是虚拟机的机器代码。


对于演练,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指令。

答案 1 :(得分:7)

  

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

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

  

Assembly和Bytecode之间的关系

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

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

答案 2 :(得分:7)

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

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

好的,首先,当你遇到单词"字节码"只是关上窗户,停止阅读,因为你走错了路;可能是最好的切线,最糟糕的是,你可能正在阅读一个试图听起来比他真实更聪明的人,将科技的流行语写入他的写作中。

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

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

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

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

快进到今天......

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

具体来说,你是"投掷"这些"蝴蝶开关" ...

  • 7 - ON
  • 6 - OFF
  • 5 - OFF
  • 4 - ON
  • 3 - OFF
  • 2 - 关闭
  • 1 - OFF
  • 0 - OFF

这里有很大的不同(今天是高科技wowee-zowee的一部分)......

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

这三个项目是 - 汇编程序 - 链接器 - 装载机

那么(我希望)这有助于你理解 OPCODE 是一系列小开关的心理代表,它们将被打开" ;或者"关闭"。

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

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

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

因此,您可以在文本编辑器中键入字母NOP,如下所示

  NOP           ;This is a No operation instruction

然后保存文件。

然后请求汇编程序汇编该文件

当汇编程序看到NOP时,他在为链接器创建的 Object 文件中创建了90(十六进制)。

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

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

无论如何,NOP90文件中的某个位置变为EXE,并且加载程序根据您不遵守的179条规则将其卡在了一个很好的区域中不得不再担心了。

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

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

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

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

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

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

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

机器代码是一堆操作码

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

答案 3 :(得分:6)

关系是:

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

machine code = opcode + operands

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

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

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

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

需要注意的是:LLVM使用存储在compacted form(也称为字节码)中的中间格式(IF)。这允许在生成本机代码

之前执行机器中性代码分析优化

答案 4 :(得分:6)

简言之:

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

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

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

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

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

答案 5 :(得分:2)

大会: 汇编程序+数据字节+运算符的人类可读教师

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

它包含:

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

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

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

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