通过汇编程序理解汇编和链接过程
一个标准C程序到最终的可执行文件包含如下几个过程:
但今天我们要从汇编程序开始讨论,所以不讨论预编译和编译过程,直接从汇编代码开始,讨论汇编和链接过程。
汇编代码简析
通过编写汇编程序,然后分析它的汇编和链接过程,对理解汇编程序中的各种汇编器指令和各种标签很有帮助。
首先介绍一下汇编器指令和标签这两个概念,观察下面一段求最大值的汇编程序代码maxmum.s:
#目的:寻找一组数中的最大值#寄存器有以下用途:
# %edi - 保存正在检测的数据的索引
# %ebx - 当前已经找到的最大数据项
# %eax - 当前数据项#使用以下内存位置:
# data_items - 包含数据项
# 0表示数据结束.section .datadata_items:
.long 3, 67, 34, 222, 45, 75, 54, 34, 44, 33, 22, 11, 66, 0.section .text
.global _start #不加.globl,表示只在本文件中有效。加了.globl修饰,表示_start标签在其他文件也有效。在多个.o目标文件链接时,其代表的地址可以被引用。_start:movl $0, %edimovl data_items(,%edi,4), %eaxmovl %eax, %ebxstart_loop:cmpl $0, %eax je loop_exit incl %edimovl data_items(,%edi,4), %eaxcmpl %ebx, %eax jle start_loopmovl %eax, %ebxjmp start_loop loop_exit:movl $1, %eaxint $0x80
在这段代码中,.section, .long就是汇编器指令,它们是伪汇编指令,是帮助汇编器和链接器做处理的,在底层没有与之对应的机器码。而movl指令是真正的汇编指令,在底层有对应的机器码,可以真正被处理器执行。
_start, start_loop, loop_exit这三个符号后面都有一个冒号“:”,表示它们是标签。汇编代码中的标签,代表的含义就是内存地址。稍后通过反汇编它们链接而成的ELF文件,可以观察到这些标签起到的作用。
接下来,将这段汇编代码使用as汇编器汇编,将所有的汇编指令翻译成机器码构成的.o目标文件:
as maxmum.s -o maxmum.o
接着,使用链接器ld将.o目标文件进行链接,由于这里不涉及库函数调用,所以需要链接的目标文件仅有一个:
ld maxmum.o -o maxmum
经过链接以后,形成了最终可以被linux内核识别的可执行文件格式ELF,其实就是将刚才汇编代码中的所有标签换成它所代表的内存地址,稍后会在反汇编时看到这一点。
使用objdump反汇编Linux的可执行文件
使用objdump工具进行反汇编,查看所有可执行节(.section)的信息:
objdump -d maxmum
可执行文件反汇编后显示如下:
注意两列红色箭头指向的信息,左面是一个内存地址(虚拟地址),右面是该地址对应的标签。在上面提到过,汇编程序中一个标签就代表一个地址,所以在这里看到的就是汇编程序中的标签对应的地址。
在汇编程序的.text节中,我们定义了3个标签,分别是_start,start_loop,和loop_exit,这正是我们在反汇编可执行文件后看到的<_start>,<start_loop>,<loop_exit>,所以,以后再看到反汇编ELF文件中的标签,就知道它们是怎么来的了。
标签替换
如果再观察反汇编后的汇编语句,对比我们写的汇编程序中的汇编语句,你有没有发现了什么?
观察下面的两张图,上面为汇编程序中的汇编代码,下面是对应的可执行文件反汇编后的结果:
- 汇编程序中的标签
- 反汇编中的标签
值得注意的是,对于标识数据段的标签,如汇编代码中的data_items,会直接替换为地址;对于标识代码段的标签,也会被替换为地址,但是在地址后面会显示对应的标签,这样在分析汇编程序时,可以更方便的观察控制流的跳转。
所以根据上面的分析,我们可以知道两点知识:
- 汇编代码中的标签代表的含义是地址,并且在汇编和链接的时候会由汇编器和链接器将标签替换为地址。
- 一般地址后没有跟其对应的标签的,这个地址是数据段(.section .data)的地址;如果地址后跟了一个标签,如
jmp 4000bf <start_loop>
,这个地址就是代码段(.section .text)中的一个标签地址。
现在我们来实际观察一个标准的C程序编译成的ELF文件反汇编后的结果:
先来看下第一张图,红线标出了三个标签,现在,我们并不关心这三个标签在实际的汇编代码中的作用,只需要知道这三个标签代表的是代码段中的地址,是在控制流跳转的时候的发挥作用。现在在看反汇编的结果时,是不是感觉清楚了一些呢。
第一张图和第二张图各用红色框圈出了两个地址,但是第一个地址0x200a91
后没有跟标签名,所以这是一个数据段的内存地址;而第二个地址5b0
后跟了一个标签<register_tm_clones>
,说明这个地址是代码段中的一个地址。
有趣的链接
链接的原理其实很简单,就是把不同的目标文件中的标签全部保存在一个新的文件中,这个新文件就是可执行文件,在linux中可执行文件的格式为ELF。
现在,我们继续看一段代码,还是刚才的计算最大值的汇编程序:
我们用保存返回值的ebx寄存器来保存最大值结果。汇编和链接以后,使用./maxmum运行。然后通过echo $?
查看刚才程序的退出状态码。结果如下:
最大值为222,程序运行正确。
在上面的汇编程序中,我们把代码段和数据段放在了一个文件中,并且在汇编的时候,不需要依赖其他的文件,所以汇编以后只有一个.o目标文件,在链接的时候,只需要链接这一个.o目标文件,这样并不能直接体会到链接的作用,所以如何再增加一个.o目标文件呢?
最简单的办法就是把上面的汇编代码拆成两个部分,将代码段和数据段分离,即变成下面这样:
- 代码段
- 数据段
代码段的汇编源程序为maxmum_text.s,数据段的汇编源程序为maxmum_data.s,如下:
分别对代码段汇编代码和数据段汇编代码进行汇编:
as maxmum_text.s -o maxmum_text.o
as maxmum_data.s -o maxmum_data.o
各生成一个.o目标文件,如下:
现在是体现链接作用的时候了,我们现在有两个目标文件了,一个是代码段的,一个是数据段的,现在我们将它们链接形成一个可执行文件,看它会不会实现上面求最大值的功能:
ld maxmum_text.o maxmum_data.o -o maxmum_test
链接之后生成了一个可执行文件:
运行然后查看结果:
可以看到,虽然我们将代码段和数据段分开了,但最后经过链接器链接后,形成的可执行文件依然实现了求最大值的功能。
所以这里链接器的作用已经很明显了,就是将不同文件中的标签组合在一个文件中,比如在代码段中,有_start标签,标识代码的起始位置,再数据段中,有data_items标签,标识数据段的起始位置,它们本来是各自存在于自己的目标文件中,但是经过链接器链接以后,这个时候再去反汇编形成的可执行文件,你会发现这些标签被组合在一起了,并且数据段标签data_items已经被直接替换成了地址0x6000dd:
但是这里需要有一点需要注意,在链接的过程中,因为要将不同文件中的标签组合在一起,所以在汇编的时候标签要通过.globl进行修饰,表示这个标签稍后会在链接过程中用到,让汇编器不要丢弃这个符号。
这里我们对比一下代码段与数据段合在一起的汇编程序中数据段的标签data_items,和单独存放数据段的汇编程序中的数据段标签data_items:
- 代码段与数据段合在一起的汇编程序中数据段的标签data_items
- 单独存放数据段的汇编程序中的数据段标签data_items
加了.globl修饰之后,汇编过程结束后,标签data_items代表的地址不会被汇编器丢弃,所以在链接的时候,当另一个文件中引用这个标签的时候,这个地址可以找到,所以引用这个地址的时候就不会出错。
相反,如果我们在将数据段分出来后,不用.globl修饰data_items标签,会发生什么呢?接下来就通过实验来观察,去掉单独存放数据段的汇编程序中的数据段标签data_items的.globl修饰:
现在我们重新进行汇编和链接,汇编只需要对数据段在汇编即可,因为代码段不涉及改动:
不出意外,在链接的时候报错了,因为在代码段生成的.o文件中,需要引用数据段生成的.o文件的data_items标签代表的地址,但是我们并没有对这个标签使用.globl进行修饰。
这个错误相信C程序员一定不会陌生,对’data_items’未定义的引用,就是当链接器想要替换这个标签的时候,不知道这个标签对应的地址。再直接一点,就是找不到存放这个数据的内存单元的编号,即内存地址,即使链接器不进行拦截,日后CPU注定在这里访问会出错,所以这种错误应当在链接的时候就要被捕获到。
关于程序的大小
当我们在查看一个C程序的大小时,究竟在查看什么?我们先使用size命令查看一个可执行文件的大小,以上面的最大值的可执行文件为例:
其中,text列显示的是代码段的大小,为45字节;data列显示的是数据段的大小,为56字节,因为在求最大值的汇编程序中,并没有未初始化的内存单元,所以bss列为空。
接下来,我们就来看一下代码段的大小和数据段的大小是怎么得来的,首先再回到求最大值的汇编源程序,看一下数据段的定义:
在data_items里时我们定义的数据区,.long表示是编译器指令,定义一个4字节的数据,这里面定义了14个数据,分别是3,67,34,222,45,75,54,34,44,33,22,11,66,0,所以数据区大小=14*4=56Byte。
现在我们把.long改为.byte,每个数据都只用一个字节来存放:
重新汇编和链接,再使用size命令查看大小:
现在数据区的大小变成了14字节。还可以使用.word来定义数据,.word表示双字(节),这时数据区大小会变为28字节。
下面,我们来看代码段的大小,先把求最大值程序的可执行文件反汇编:
中间的一列是由十六进制数字表示的字节码,一个字节码表示一个字节。后面的黄色数字表示该行有几个机器码,即几个字节。现在,把这些字节数相加:5x2+7x2+2x8+1x2+3=45,再对比反汇编中代码段的大小:
所以,代码段的大小就意味着程序的代码量。
有趣的世界,隐匿在01背后
这就要从机器码说起,机器码是由01组成的序列,长这样:0101 10001,这里面0代表低电平,1代表高电平,是数字信号,但是处理器是电驱动的(想想你的计算机为什么经常用一会就没电了),所以01数字信号需要经过数模转换器转换成模拟信号,即电信号,然后驱动着处理器内部的逻辑电路工作,逻辑电路是由与门,或门和非门组成的复杂的逻辑门电路,本质就是通过电流的通断来控制晶体管开关的闭合,进而控制不同的与门,或门和非门工作。处理器的内部有ALU(算术逻辑单元,实际执行指令的地方),寄存器,这些功能单元其实就是由与门,或门和非门组成的逻辑门电路。通过电流控制这三种门组成的逻辑电路工作,就可以实现寄存器的存数,和ALU进行加法进位等过程,这也是计算机运行时,处理器内部真实发生的事情。
现在,通过上面的描述,你应该了解了处理器内部是怎么工作的了,关于这个问题,即计算机是怎么运行起来的,可以看我之前写过一篇文章:计算机是怎么运行的?为什么它可以自动化的工作?这和时钟信号又有什么关系?
理解了计算机是怎么工作的之后,相信你也深刻体会到了机器码的意义。
以上就是关于在汇编程序中理解理解汇编和链接过程一点小小总结。
通过汇编程序理解汇编和链接过程相关推荐
- GCC 编译 C++ 程序分步骤流程(预处理 gcc -E、编译 gcc -S、汇编 gcc -c 和链接 gcc 以及 gcc -o 选项)
C 或者 C++ 程序从源代码生成可执行程序的过程,需经历 4 个过程,分别是预处理.编译.汇编和链接. 同样,使用 GCC 编译器编译 C 或者 C++ 程序,也必须要经历这 4 个过程.但考虑在实 ...
- GNU ARM汇编--(二)汇编编译链接与运行
GNU的汇编器是GNU Tools的一部分,可以用来ARM的汇编语言源代码编译为二进制文件.关于GNU汇编器的介绍可以搜索<GNU Assembler Manual>.这里我们只是做一个简 ...
- 程序的编译和链接过程
一.虚拟机.linux简介 简单介绍一下虚拟机还有就是各种操作系统,比如centos,Ubuntu 操作系统:linux(centos.Ubuntu.redhat),Android,Windows(x ...
- 编译/链接过程如何工作?
编译和链接过程如何工作? (注意:这本来是Stack Overflow的C ++ FAQ的条目.如果您想批评以这种形式提供FAQ的想法,那么在所有这些都开始的meta上的张贴将是这样做的地方.该问题在 ...
- C语言的编译链接过程详解
学过C语言的人都应该知道,我们所编辑的C语言程序是不能直接放到机器上运行的,它只不过是一个带".c"后缀的文件(也称为源代码)而已,需要经过一定的处理才能转换成机器上可运行的可执行 ...
- c语言程序链接过程,C语言简明教程(二):C程序编译链接过程和实例对照详解...
不像高级编程语言,在C语言开发中,了解其编译链接过程显得相对重要,因为C语言是较为底层的语言,很多时候我们调试C程序或者解决其它问题都可能会涉及到C编译链接的相关知识,例如编译动态库或者静态库.下面我 ...
- C语言的编译链接过程的介绍
发布时间: 2012-11-08 10:17 作者: 未知 来源: 51Testing软件测试网采编 字体: 小 中 大 | 上一篇 下一篇 | 打印 | 我要投稿 | 推荐标 ...
- 【C语言关键知识点1】C语言的预处理、编译和链接过程
1 引言 再一次回顾C语言的关键基础知识,今天带大家深刻的剖析一下C语言的预处理.编译(汇编)和链接的过程,以加深对C语言及编程本质的理解! 学习C语言首先要理解的就是如何将程序员输入的源代码 ...
- windows环境下gcc的使用(二):gcc命令与程序编译链接过程
测试Linux命令 上一篇博客已经安装好了cygwin,相当于在windows平台上已经搭建好了一个模拟Linux的环境,那么在cygwin的终端(Cygwin64 Terminal)中测试Linux ...
- C/C++的编译和链接过程
目录 从源文件生成可执行文件(书中第2章) 1.Preprocessing预处理--预处理器cpp 2.Compilation编译--编译器cll ps:vs中优化选项设置 3.Assembly汇编- ...
最新文章
- 32.ExtJS简单的动画效果
- vector的基本用法 (详解 + 代码演示)
- 将PPT内容导出为JPG图片
- mingw c++ 命令行_Mingw-w64在win10下的安装使用
- Windows下链接boost库及应用实例
- LivePlayer.js免费直播、点播播放器如何自适应div宽高集成播放视频
- python创建线程函数_Python多线程编程(三):threading.Thread类的重要函数和方法...
- Python使用总结
- [bzoj3509][CodeChef]COUNTARI
- TrueCrypt的原理
- 怎样选择图纸加密软件?
- PHP通过地址获取经纬度
- Window API 第五篇 WTSEnumerateProcesses
- 【麦课】1~OEL的下载
- 制作集成SATA、RAID和AHCI驱动的Windows XP sp3 安装光盘
- 微博签到数据——北京、上海、昆明、深圳(2018-2022已更新完毕)
- 耳机驱动调试(插拔检测与按键检测)
- Java线程(五):Executors、ThreadFactory
- 使用ps完成手写数字图片(用于验证手写数字模型或制作数据集)
- 1103 缘分数 – PAT乙级真题