在区分源代码,对象代码,汇编代码和机器代码时,我有一个困惑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在区分源代码,对象代码,汇编代码和机器代码时,我有一个困惑相关的知识,希望对你有一定的参考价值。

我阅读了编写源代码的每个地方(高级语言),编译器将其转换为机器代码(低级语言)。然后我读到有一个汇编程序,它将汇编代码转换为机器代码。然后在区分编译器和解释器时,我读到编译器首先将整个代码转换为目标代码,而解释器通过跳过目标代码直接转换为机器代码。现在我有困惑,我想到了以下问题:

  1. 从汇编代码出来的地方,如果编译器直接将源代码转换为机器代码?
  2. 目标代码和机器代码有什么区别?
  3. 谁将源代码转换为汇编代码?
  4. 什么是高级和低级语言,如何区分它们?
  5. 汇编代码和目标代码是高级还是低级?
答案

大多数问题都没有简单的答案,因为它可能因编译器而异。一些编译器会发出其他高级语言,例如C.

  1. 通常,对于使用汇编程序的编译器,后端将发出临时的asm文件,汇编器将其转换为目标代码。如果您有权访问GCC,您可以看到它与-v选项一起使用的命令链。例如,对于C源 int main(){ return 1; } 命令

gcc -v -o test test.c

输出(我已经过滤了很多)

cc1 test.c -o /tmp/cc9Otd7R.s
as -v --64 -o /tmp/cc5KhWEM.o /tmp/cc9Otd7R.s
collect2 --eh-frame-hdr -m elf_x86_64 -o test /tmp/cc5KhWEM.o
  1. 对我来说,目标代码是以机器和OS体系结构所需的格式发出的二进制代码。例如,这可以是以部分排列的ELF格式。机器代码只是汇编程序的二进制表示。例如这一点反汇编

48 83 ec 10 sub rsp,0x10

前四个字是机器代码的4个字节,后面是汇编程序。

  1. 根据第1点,这将是编译器后端。
  2. 这有些主观,但装配水平较低。您通常不会手动修改目标代码(我有时使用十六进制编辑器这样做但这些更改通常非常小)
另一答案

汇编程序采用汇编语言,处理器指令更易于人类读写,并将其转换为机器代码或这些指令的二进制版本。

汇编语言向量

.thumb

.globl _start
_start:
.word 0x20001000
.word reset
.word foo
.word foo
.word foo
.word foo
.word foo
.word foo

.thumb_func
reset:
    bl fun
.thumb_func
foo:
    b foo

.globl dummy
dummy:
    bx lr

组装然后拆卸

arm-none-eabi-as vectors.s -o vectors.o
arm-none-eabi-objdump -D vectors.o > vectors.list

拆卸的相关部分

Disassembly of section .text:

00000000 <_start>:
   0:   20001000
    ...

00000020 <reset>:
  20:   f7ff fffe   bl  0 <fun>

00000024 <foo>:
  24:   e7fe        b.n 24 <foo>

00000026 <dummy>:
  26:   4770        bx  lr

.words不是指令,它们是将数据放入二进制/输出的方法。在这种情况下,我正在生成一个向量表。反汇编程序还没有显示所有内容,我们将看到其余部分。汇编程序已经留下了占位符,我们很快就会看到链接器要填充。所以这就是对象看起来像程序集已经变成机器代码了。程序集bx lr,机器代码0x4770

规则有例外,通常是出于特定原因,但通常没有必要让编译器直接编译为机器代码。你必须有一个目标的汇编程序,所以它已经存在,使用它。编译器编写器调试汇编代码要比调试机器代码容易得多。有一些例外,有“只是因为我想”有点像你为什么爬山而不是四处走动“因为它在那里”。然后有及时的原因和其他一些原因。 JIT需要更快地获得机器代码,或者使用一个工具/库/驱动程序等等......所以你可能会看到那些跳过这一步,开发起来就更难了。通常你可以通过重命名你的汇编程序来测试这个理论(虽然必须打到正确的二进制文件,你在命令行上运行的那个可能是真实的一个,实际上在gcc的情况下我认为gcc我们使用的程序是只是cc1的前端,也许是另一个或两个程序以及汇编器和链接器,所有这些都是从gcc中产生的,除非你告诉它不要)。

所以我们采取简单的入门计划

#define FIVE 5
unsigned int more_fun ( unsigned int );
void fun ( void )
{
    more_fun(FIVE);
}

arm-none-eabi-gcc -mthumb -save-temps -O2 -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o > fun.list

第一个临时是预处理器采用#​​defines和#includes并基本上删除它们,生成将被发送到编译器的文件

# 1 "fun.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "fun.c"


unsigned int more_fun ( unsigned int );
void fun ( void )
{
    more_fun(5);
}

然后调用编译器本身并编译为汇编语言

    .cpu arm7tdmi
    .fpu softvfp
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .code   16
    .file   "fun.c"
    .text
    .align  2
    .global fun
    .code   16
    .thumb_func
    .type   fun, %function
fun:
    push    {r3, lr}
    mov r0, #5
    bl  more_fun
    @ sp needed
    pop {r3}
    pop {r0}
    bx  r0
    .size   fun, .-fun
    .ident  "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"

然后调用汇编程序将其转换为一个对象,我们可以在对象的反汇编中看到这里生成的内容:

Disassembly of section .text:

00000000 <fun>:
   0:   b508        push    {r3, lr}
   2:   2005        movs    r0, #5
   4:   f7ff fffe   bl  0 <more_fun>
   8:   bc08        pop {r3}
   a:   bc01        pop {r0}
   c:   4700        bx  r0
   e:   46c0        nop         ; (mov r8, r8)

现在bl 0还不是真的,more_fun是一个外部标签,所以链接器必须进入并修复它,我们很快就会看到。

more_fun.c同样的故事

源代码

#define ONE 1
unsigned int more_fun ( unsigned int x )
{
    return(x+ONE);
}

编译输入

# 1 "more_fun.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "more_fun.c"


unsigned int more_fun ( unsigned int x )
{
    return(x+1);
}

编译器输出(汇编器输入)

    .cpu arm7tdmi
    .fpu softvfp
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .code   16
    .file   "more_fun.c"
    .text
    .align  2
    .global more_fun
    .code   16
    .thumb_func
    .type   more_fun, %function
more_fun:
    add r0, r0, #1
    @ sp needed
    bx  lr
    .size   more_fun, .-more_fun
    .ident  "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"

反汇编对象(汇编程序输出)

Disassembly of section .text:

00000000 <more_fun>:
   0:   3001        adds    r0, #1
   2:   4770        bx  lr

现在我们将所有这些链接在一起(有一个原因,它被称为工具链,编译,汇编,链接一系列链接在一​​起的工具,一个输出的一个输入另一个的输入)

arm-none-eabi-ld -Ttext=0x2000 vectors.o fun.o more_fun.o -o run.elf
arm-none-eabi-objdump -D run.elf > run.list
arm-none-eabi-objcopy -O srec run.elf run.srec


Disassembly of section .text:

00002000 <_start>:
    2000:   20001000 
    2004:   00002021 
    2008:   00002025 
    200c:   00002025 
    2010:   00002025 
    2014:   00002025 
    2018:   00002025 
    201c:   00002025 

00002020 <reset>:
    2020:   f000 f802   bl  2028 <fun>

00002024 <foo>:
    2024:   e7fe        b.n 2024 <foo>

00002026 <dummy>:
    2026:   4770        bx  lr

00002028 <fun>:
    2028:   b508        push    {r3, lr}
    202a:   2005        movs    r0, #5
    202c:   f000 f804   bl  2038 <more_fun>
    2030:   bc08        pop {r3}
    2032:   bc01        pop {r0}
    2034:   4700        bx  r0
    2036:   46c0        nop         ; (mov r8, r8)

00002038 <more_fun>:
    2038:   3001        adds    r0, #1
    203a:   4770        bx  lr

链接器已调整外部标签,在这种情况下,通过修改指令以获得正确的偏移量。

   4:   f7ff fffe   bl  0 <more_fun>
202c:   f000 f804   bl  2038 <more_fun>

elf文件格式是一种“二进制”文件,它是二进制文件,你用文本编辑器打开它,你看到一些文本,但大多是垃圾。还有其他“二进制”文件格式,如motorola s-record,在这种情况下只包括真实的东西,机器代码和任何数据,其中elf有调试信息,如字符串“fun”“more_fun”等,反汇编程序碰巧曾经使输出更漂亮。 Motorola S-Record和Intel Hex是ascii文件格式,如下所示:

S00B000072756E2E73726563C4
S113200000100020212000002520000025200000D1
S113201025200000252000002520000025200000A8
S113202000F002F8FEE7704708B5052000F004F858
S10F203008BC01BC0047C04601307047EA
S9032000DC

不再使用,但不是完全无用,过去需要这种格式来编程rom,工具制造商的个人偏好,他们支持哪种文件格式。如何将二进制文件刻录到微控制器的闪存中?有些工具从主机/开发机器中获取这些位,并通过某些接口和一些软件将其移动到目标,该工具支持哪些二进制文件格式?由任何人编写工具来选择一种或多种格式。

在编译器以各种方式获得成本之前(购买成本和/或用于在计算机上保存程序的存储空间,加上中间数据等),可以使用汇编程序来制作整个程序。你会看到像.org 100h这样的指令,带有“工具链”的汇编程序可能具有该功能,但作为链的一部分,汇编程序工具需要从汇编语言转换为对象格式,大多数转换为机器代码和其他数据。当然,编译器可以完成所有工作并输出完成的二进制文件是可能的,当工具链的一部分时,理智的方法是最终从源代码到汇编语言。我们习惯使用的编译器工具,gcc,msvc,clang等,除非另有说明,否则将为我们以及编译器生成汇编器和链接器,使编译器看起来像编译器从一个神奇的步骤从源到最终二进制。链接器接收一些具有未解析的外部标签的单个对象,并决定内存映像中的位置,内存中的位置,根据需要解析外部。链接器做了多少是这些工具的系统设计的很大一部分,设计可以是这样的,链接器不会修改它只在一致的地方放置地址的单个指令。一个例子:

vectors.s

.globl _start
_start:
    bl fun
    b .
.global hello
hello: .word 0

fun.c

#define FIVE 5
extern unsigned int hello;
void fun ( void )
{
    hello+=FIVE;
}

fun.o反汇编

Disassembly of section .text:

00000000 <fun>:
   0:   e59f200c    ldr r2, [pc, #12]   ; 14 <fun+0x14>
   4:   e5923000    ldr r3, [r2]
   8:   e2833005    add r3, r3, #5
   c:   e5823000    str r3, [r2]
  10:   e12fff1e    bx  lr
  14:   00000000    andeq   r0, r0, r0

所以我们可以看到它从偏移量/地址0x14加载到一个数字到r2然后该数字用作一个地址来获取hello,然后读取的内容有5添加到它然后r2中的地址用于将hello保存回记忆。那么在0x14处是编译器留下的占位符,因此链接器可以将地址放在hello那里,我们看到它们一旦链接

Disassembly of section .text:

00002000 <_start>:
    2000:   eb000001    bl  200c <fun>
    2004:   eafffffe    b   2004 <_start+0x4>

00002008 <hello>:
    2008:   00000000    andeq   r0, r0, r0

以上是关于在区分源代码,对象代码,汇编代码和机器代码时,我有一个困惑的主要内容,如果未能解决你的问题,请参考以下文章

如何在反汇编的 ELF 可执行文件中查找(区分)库代码?

本机代码、机器代码和汇编代码有啥区别?

程序编译流程

GDB调试汇编分析

如何区分代码与数据字节 pefile

深入理解计算机系统(第二版)----之三:程序的机器级表示