欢迎光临小说阅读网-爱阅读

当前位置: 短篇原创文学>灵异

程序的前世今生編譯鏈接和加載簡介

2019-12-06 20:35:33 作者: 0人读过 | 我要投稿

本篇文章是组内分享的小结,主要介绍源代码 - 可执行程序 - 执行这一过程也就是源代碼是如何轉化為可執行程序,然后可執行程序又是如何執行的在用java或python时,只需要java ClsName或者python 就可以执行相应的程序,实际上它们都是依托于底层的虚拟机本文主要介绍的是操作系统级别的连接、加载、执行等,而不是虚拟机语言的执行这里只对链接、加载进行一个简介,详细内容推荐大家去看《深入理解计算机系统》和《程序员的自我修养》,第二本要比第一本讲的更加详细,但稍显啰嗦,如果只是了解建议阅读第一本的第七章

先看两个示例程序,后续会以它们为例:

//foo.c #includestdio.h inta=10; intb; voidbar(intc); int main(){ bar(a); printf(...); } //bar.c voidbar(intc){ //... }通常c程序是由多个模块组成的,每个模块对应一个c文件,会被编译成可连接目标文件,然后由连接器将所有的模块组合成一个可执行程序可以通过下面命令完成编译动作:

gcc-cfoo.c编译之后当前目录会生成foo.o,就是对应的可连接目标文件实际上由源代码转化成目标文件是由多个步骤组成的:

预处理(cpp):完成宏替换、文件引入,以及去除空行、注释等为词法分析准备

编译(cc):将预处理后的代码编译成汇编代码,由于加入了汇编器这一层,隔离了底层硬件的不同实现,提高了移植性

汇编(as):将汇编代码转化成机器码,也就是01序列

我们知道,一个程序是由代码和数据组成的,目标文件必须以某种方式组织这些信息,以便链接器和加载器从文件中去识别相应的信息在Linux下,目标文件的格式是ELF(Executable Linkable Format),可以用来描述可链接目标文件、可执行目标文件盒共享目标文件下面就来看看可链接目标文件中主要包含什么内容目标文件以节(Section)组织数据,同时具有一个节头部表(Section Header)用来描述所有的节主要的节包括:

.data:已初始化的全局变量和静态局局变量foo.c中的全局变量a就是存在.data节中

.bss:未初始化的全局变量和静态局部变量,这个节在载入内存时会被清0,所以未初始化的全局变量和静态局部变量默认值是0foo.c中的全局变量b存在.bss节

.text:编译后的机器代码所有的函数编译后的二进制代码会存在.text节中,比如main函数

.string:用来存储目标文件中用到的字符串以及字符串常量

.symtab:符号表符号就是目标文件中的全局变量和函数,符号表描述目标文件中的所有符号,这个是链接器进行链接的基础符号分为:

导入符号:当前模块引用其他模块中定义的符号,比如:在foo.c中使用的bar.c中定义的bar函数,那么foo.o的符号表就包含导入符号bar

导出符号:就是当前模块定义的符号,可以被其他模块引用这些导出符号就是模块中定义的初始化的全局变量和非静态函数

目标文件中其实还有很多个节,这里只介绍上面几个主要的节

多个c文件分别编译成可链接的目标文件后,要生成可执行文件那么还需要进行链接链接就是解决多个模块的引用和库调用,然后进行重定位以便生成可执行文件链接过程最重要的就是符号解析,就是将模块中的导入符号找到其定义的地方,然后将符号替换为指针

在链接时,符号可以分为强符号和弱符号:

强符号:就是初始化的全局变量和非静态函数比如,foo.c中的全局变量a和函数main以及bar.c中的函数bar

弱符号:未初始化的全局变量比如,foo.c中的全局变量b

链接时,如果遇到重名的强符号(比如在foo.c和bar.c中都定义了int a = 1;),会报错duplicated symbols,具体名称记不清了如果遇到重名的弱符号,链接的行为取决于具体实现,这里不再深入讨论

链接器需要把多个可链接目标文件组合形成一个可执行目标文件,它会收集各个模块中相同类型的节然后组成可执行文件的对应的节,比如:收集foo.o和bar.o的.data节,然后合并在一起组成可执行文件的.data节链接器还需要完成重定位,因为在合并节时,原来模块节的地址会改变,所以重定位就是修改模块中指针的地址

完成连接之后,在磁盘上就会生成可执行目标文件要执行一个程序时,必须要把可执行目标文件载入内存我们知道,进程是程序执行的容器,每个运行的程序都有自己的内存地址空间,需要将可执行目标文件中数据和代码节载入到进程的地址空间下面看一下进程的地址空间:

每个进程都有自己私有的虚拟内存地址空间,在32bit机器上,地址空间的大小是4GB,高地址的1GB的内存空间被映射为内核空间,用于提供内核服务用户栈就是函数调用栈用于实现函数调用,在栈上为局部变量分配空间共享库用于实现类似C标准库的代码和数据堆用于动态内存分配剩下的数据区和代码区是与可执行文件相关的,需要从磁盘加载

加载器载入可执行目标文件时需要虚拟存储器的支持,通过mmap的文件映射方式将可执行目标文件中的.data节和.bass节映射到进程地址空间中的数据区,将.text节映射到代码区栈和堆采用的是mmap的匿名映射,也就是没有提供文件参数地址空间中的每个被占用的区域就是一个VMA(Virtual Memory Area),这些VMA会通过链表和红黑树组织起来,采用链表是为了便于顺序遍历,红黑树是为了根据地址快速检索到对应的VMA当我们通过一个地址p访问内存时,os会进行地址合法性检查,第一必须保证p包含在某个VMA中;第二对于每个VMA都有一个读、写和执行的权限,进程必须具备相应的权限才能执行操作如果不满足上述两点,就会抛出Segment Fault错误

在完成映射之后,接下来开始执行程序,首先执行的是_start函数这个是属于glibc的库函数,完成程序的初始化,为程序的运行准备,接下来就会调用main函数,这是通过符号表完成main函数入口的定位经过mmap实际上文件并没有载入内存,当第一次访问时会从磁盘加载对应的内容,这是由虚拟存储器机制完成的,对进程是透明的

用户栈中的元素就是一个个函数调用对应的栈帧,当前栈帧有CPU寄存器%esp和%ebp标识,%esp是栈指针指向栈顶,%ebp是帧指针,在%esp和%ebp之间的区域就是当前函数对应的栈帧栈帧存放的是函数的实参已经局部变量

运行时堆是动态内存分配的区域,指针sbrk指向堆顶,可以通过改变sbrk进行动态内存的分配和释放C标准库中的malloc和free底层就是基于sbrk指针,当然我们也可以通过sbrk实现内存分配器,不过还要设计自己的内存分配算法,通常建议还是使用标准库进行内存分配

上面就是小组分享的全部内容,由于完全裸讲,没有充分准备,想到那讲到那,所以难免会有纰漏,见谅啊~~

生物谷药业
勃起功能障碍该怎么办
脑动脉硬化检查
经常心绞痛

本文相关的其他文章