在区分源代码,对象代码,汇编代码和机器代码时,我有一个困惑
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在区分源代码,对象代码,汇编代码和机器代码时,我有一个困惑相关的知识,希望对你有一定的参考价值。
我阅读了编写源代码的每个地方(高级语言),编译器将其转换为机器代码(低级语言)。然后我读到有一个汇编程序,它将汇编代码转换为机器代码。然后在区分编译器和解释器时,我读到编译器首先将整个代码转换为目标代码,而解释器通过跳过目标代码直接转换为机器代码。现在我有困惑,我想到了以下问题:
- 从汇编代码出来的地方,如果编译器直接将源代码转换为机器代码?
- 目标代码和机器代码有什么区别?
- 谁将源代码转换为汇编代码?
- 什么是高级和低级语言,如何区分它们?
- 汇编代码和目标代码是高级还是低级?
大多数问题都没有简单的答案,因为它可能因编译器而异。一些编译器会发出其他高级语言,例如C.
- 通常,对于使用汇编程序的编译器,后端将发出临时的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
- 对我来说,目标代码是以机器和OS体系结构所需的格式发出的二进制代码。例如,这可以是以部分排列的ELF格式。机器代码只是汇编程序的二进制表示。例如这一点反汇编
48 83 ec 10 sub rsp,0x10
前四个字是机器代码的4个字节,后面是汇编程序。
- 根据第1点,这将是编译器后端。
- 这有些主观,但装配水平较低。您通常不会手动修改目标代码(我有时使用十六进制编辑器这样做但这些更改通常非常小)
汇编程序采用汇编语言,处理器指令更易于人类读写,并将其转换为机器代码或这些指令的二进制版本。
汇编语言向量
.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
以上是关于在区分源代码,对象代码,汇编代码和机器代码时,我有一个困惑的主要内容,如果未能解决你的问题,请参考以下文章