又开了新的坑,这学期的目标就是 操作系统+汇编语言(8086)+链接+Cpp, 希望能完整了了解在操作系统下,一个c程序从运行到结束这背后的故事。
笔记主要是第二章和第三章,编译和链接初步及目标文件里有什么。
一.从.c到a.out
以Linux为例,我们写一个程序,最简单就是gcc 1.c 然后生成一个可执行的a.out文件,这中间经历了 预处理—编译—汇编—链接 的过程。
预处理:
将宏定义展开,如#define PI 3.14 那么在代码中的PI 都会变成3.14.
将条件编译处理,如#ifdef等。
将#include包含的文件插入,一个简单的helloworld程序也会包含stdio.h那几百 行代码。
将注释删除。
添加行号和文件名标记,便于编译器生成调试用的行号信息。
命令:gcc -E test.c -o test.i
编译:
对代码进行词法分析、语法分析、语义分析等,生成汇编代码,这中间编译器还进行了很多优化。
命令:gcc -S test.i -o test.s
汇编:
将汇编代码生成机器代码。
命令:gcc -c test.s -o test.o
链接:
我的理解就是将库和目标文件链接起来,使得程序能找到调用的库函数。
二.为什么需要链接
最早的程序都是直接在纸上打孔,直接写机器语言的,也就是一条条指令。指令的执行可能会有跳转,但如果每次跳转都是绝对地址,一旦程序有所改变,意味着所有的地址都发生了变化,必须要一条条的改。可以类比宏定义,比如你不使用宏定义一个常量,万一需要改变它的值,就要把每个使用到它的地方都改一遍,非常麻烦。
但如果我们将跳转的位置用一个名字表示,每次都由机器去计算名字代表的地址是多少来找到正确的地址就很方便了。
一个很大的程序是由多个不同的模块组成,将各个模块组装起来的过程就是链接,而模块之间的互相调用要找到正确的地址,这就是链接器帮我们完成的,找到跳转需要的正确地址,方便了我们的开发。
三.目标文件里有什么
目标文件就是代码经过上面前三部后生成的文件,从结构上讲,它和可执行文件的结构稍有,只是还没有链接,无法执行,但就是按照可执行文件的格式进行存储的。
PC平台流行的可执行文件格式主要是Linux下的ELF和Windows下的PE。他们都有共同的祖先UNIX下的COFF格式。
目标文件中的信息主要是按照不同的属性,以段(segment)的格式进行存储的,也叫节,略有不同,后面针对再说。像我们知道的代码段(.text)和数据段(.data),为什么是段呢,将指令和数据分开,集中到一起,方便对其设置权限,比如代码只读,数据可读写。最重要的是将指令共享,节省内存空间 。我自己还有一种想法,就是在学习汇编语言时,也是按照段编写程序的,CPU不知道哪是代码,哪是数据,把它们分类,让CPU方便执行。
以ELF文件为例,主要分为两部分,文件头和各种段。我们主要使用readelf和objdump来查看目标文件。
这里我们写了一个非常简单的程序simplesection.c作为例子
/*************************************************************************
> File Name: simplesection.c
> Author: Jack Kang
> Mail: kangyijie@xiyoulinx.org
> Created Time: 2016年11月07日 星期一 14时16分54秒
************************************************************************/
int printf(const char *format, ...);
int global_init_var = 84;
int global_uninit_var;
void func1(int i)
{
printf("%d\n",i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
文件头结构位于/etc/elf.h,主要有文件类型,平台属性,程序入口,段表的位置及长度,段的个数,其他信息我们此处忽略,有兴趣可以自行研究。
我们用readelf查看文件头的信息
文件类型分为四类,可重定位文件(目标文件,静态库),可执行文件,共享目标文件(动态库),核心转储文件。
平台属性,在elf.h中有大量的宏,包括386 ARM等。
程序入口,操作系统加载完程序后,从这里开始执行进程指令。
段表的位置及长度,段表是ELF文件中除了文件头最重要的结构,在段表中记录了各个段的名字,位置,长度和段的属性,ELF的段结构就是由段表定义的,编译器,链接器,装载器都是通过段表找到段的和访问段的。段表是以以结构体“Elf32_Shdr”(32位)为元素的数组。“Elf32_Shdr”又被称为段描述符。
那么让我们各个段的信息看一看
这些段的名字都是以‘.’开头的,表示这些段的名字是系统预留的,我们也可以通过objcopy将自己的文件作为一个段插入其中。
第一个段是默认的什么都没有,我们不去管他。
段名后面就是类型,出现的几个类型含义如下:
NULL 无效段。
PROGBITS 程序段。 代码段、数据段都是这样的类型。
SYMTAB 符号表
RELA 重定位表
STRTAB 字符串表