5.1  GCC游记 kswiss

5.1.0  声明

声明:

a、除非另外声明,文档的版权是属于其作者的。对于《GCC游记》所有文档作者保留所 有权利。

b、《GCC游记》允许每个人的非商业化地引用和参考,但引用、转载、参考时必须注明 作者真实姓名或者在浙江大学飘渺水运间bbs站点上的帐号kswiss,以及出处“浙江大 学飘渺水云间”,无须向作者支付任何费用,自由文档版权声明神圣不可侵犯,使用者 不得对声明中的任何条款作任何形式的修改,也不得附加任何其它的条件。

c、任何人允许在争得作者同意的前提下对该文档进行修订后再复制和发布。如果争得 作者同意后,您修订了自由文档或者发布了修订版本的副本,您就不再是一般意义上的 使用者而成为修订人,这些规定就转化为你的责任。修订人应遵循如下规定:部分,修 订者拥有其版权。

2。保护原作者以及前期版本的修订者的署名权。修订者应在修订版本中保留原作 者、 出处以及前期修订者的姓名及联系方式

3。专门建立并保留一部分内容名为“历史纪录”,针对每一版本的修订,记录该版本 的作者,修订时间,原出处,联系方式及发布者等相关信息

4。修订者有告诉读者如何获得上一版本文档的义务,如果是在网站上发布,应做 出指 向上一版本文档位置的链接

d、任何人可以引用本文档中的一部分内容,或是将一部分本文档内容与其他文档 内容 混合在一起使用,条件是必须在引用的文档部分中加入本版权声明。对上述行 为可依 照 c 项规定办理

e、转译。如果需要根据本计划中文档转译为其它种语言,此种行为将被视同为对 修订 版本的再次修订,可依照 c 项规定办理。

5.1.1  一

各位好!我想在ZJU应该有很多人比我更有资格和实力来全面介绍GCC的内部结 构。而今我在此的班门弄斧第一为了兴趣,第二为了骗几个MASK而已。

此次旅游你们将付出什么?时间、耐心和毅力,就这么简单。GCC是一个含蓄而动 人的地方,而我,这个导游,将带领你们浏览它的每个景点(如果我没有被旅游公 司勒令卷铺盖滚蛋的话)。如果你有足够的毅力去发现它的美,就会发现它是如此 的炫目。OK,估计你们这些缺乏幽默感的人会迫不及待的略过这些废话,毕竟GCC才 是我们所要游览的地方,而不是我这个并不帅气的导游,那好,让我们开始吧。

不要告诉我你的Linux上没有GCC,那是个灾难,因为你什么都干不了。果真如 此的话,那就找个坑把自己埋了吧!GCC是什么呢?大多数人都会认为是Gnu C Compiler.不错,当初Stallman创建GCC第一个版本的时候仅仅支持C语言, 并且在诸多方面都偏向于C和类C语言。但如今的GCC则囊括了FORTRAN、ADA等其他 语言,所以GCC更应该指代Gnu CompilerCollection。而在本次游览中,参观的重 点依然只是传统意义上的GCC。

在出发之前,我先介绍一下编译器为何物。编译器是一个程序,它能将某种代 码(源代码)所描述的程序转化为另一种代码(目标代码)描述的程序,这两个程 序必须是等价的,并且在转化过程中,编译器必须为用户提供错误信息。通常来说 我们将编译器分成四块:

 
                    刚编写完成的代码 (.c 文件) 
            ---------------------- 
            |   Preprocess(CPP)  | 
            ---------------------- 
                     | 源代码 (.i文件) 
            ---------------------- 
            |        CC1         | 
            ---------------------- 
                     | 目标汇编代码 (.s文件) 
            ---------------------- 
            |     Assembler      | 
            ---------------------- 
                     | 机器相关二进制文件(.o文件),未做relocation 
            ---------------------- 
            |      Linker        | 
            ---------------------- 
                        relocation后的机器相关二进制代码 (可执行文件) 

1.CPP即为C Preprocessor,主要用于头文件的处理,宏替换,注释的删除。 如果你不服气的话,可以试试

cpp -DA_MACRO=42 try.c -o try.i;

2.CC1,是整个编译器的核心部分,它将C代码(或者其他高级语言)转化为汇 编代码,加参数-S即可抵达。至于为什么叫CC1,我猜是C Compiler 1st-step吧, 天晓得,说不定是Stallman心血来潮;

3.Assembler,从它名字上就知道是汇编器,无需废话,不过值得注意的是在 GCC设立的景点为GAS(Gnu Assembler)。

4.Linker,连接器(LD),GCC中的另一大景点GLD;

由于CC1的景色是如此之美轮美奂,当地人在赞叹之余讲Compiler的大名赠给 了CC1(更有甚者干脆叫它为GCC)。为了对这些保护GCC美景的人表示尊重,也为 了不让自己在诸多名称中迷惑,请大家在参观CC1的时候,别忘了称之为Compiler。 而其他的景点则为GAS和GLD。顺便说一下,当地人脾气不太好,要是你叫错了,他 们可能会拿板砖拍你,小心小心!

    让我们来看看本次旅游的游览图吧。 
    CC1---目录文件结构 
       |--交叉编译器 
       |--CC1前端介绍 
       |--CC1后端介绍 
         |--RTL 
         |--机器描述语言 
 
    GAS---目录文件结构 
        |--基本原理 
        |--代码分析 
        |--数据相关性 
 
    GLD---目录文件结构 
        |--基本原理 
        |--代码分析 

好了,很晚了,我这个导游也该下班了(或许我该向老板申请加工资),明天 我们将走入GCC的世界。

5.1.2  二

早上好,各位!希望昨天晚上的雷雨没有影响大家的休息。我是导游kswiss,在本次旅 途中将自始至终地陪伴各位。好了,请大家排队上车,我们将前往GCC的最为迷人的 Compiler(CC1)。昨天晚上我已经在旅馆里向各位介绍了CC1的伟大意义,而今天就让 我们一起走入这个神奇的地方吧。

大家现在手上拿着的就是GCC的地图,我们这辆车的司机是个菜鸟,车开得比蜗牛 还慢,所以我想我有足够的时间来讲述一下GCC地图。

    让GCC根目录下的文件见鬼去吧,看看它们的文件名就能猜个大概: 
        gcc                 GCC的主要代码尽在gcc 
        boehm-gc            Java运行库的内存垃圾搜集 
        contrib             与man相关的一些脚本 
        include             libiberty库的头文件 
        lib...              各类库 
        maintainer-scripts  不知道干吗的,就是一堆脚本 
        zlib                zlib的库? 

让我们进入其中的gcc目录。在gcc目录下,按照每个文件目录的作用及其流程, 大致可以得到如下示意图:

1.Top Level                    toplev.c        作为程序的大门, 
						它主要用于初始 
                                               化、处理参数设置 
					       、文件的打开和 
					       关闭以及流程的安 
					       排等 
                                    | 
2.Parsing               tree.c  fold-const.c    stor-layout.c 
                                                主要处理一些与语 
						言无关的文件 
                        c-common.def    c-format.c  c-opts.c ... 
                                             这一些列的c-*.c文件 
					     用于遍历源程序 
                                    ^ 
                                    | 
3.Tree Optimization     tree-inline.c   fold-const.c 
                                            对语法树的优化, 
					    优化无处不在 
                                    ^ 
                                    | 
                       sibcall.c           同属优化,尽量减少同 
		                           一函数的调用跳转优化 
                       jump.c              ,删除未被引用的(指 
		                           永远不会被跳转到的) 
					   Label和无法抵达的代码 
                       regclass.c          寄存器扫描/申请等操作 
                            ... 
                       gcse.c              根据是否按照大小优化, 
		                           实现RTL子表达式的 
					   删除诸多优化策略 
                            ...                 
                       combine.c           合并指令 
                            ... 
 
                       sched.c             指令调度,我想应该和 
		       			   流水线有关 
                                    ^ 
                                    |           机器相关代码 
                        config/tm.md            机器描述 
                        config/tm.h             面向机器的宏定义 
                        config/tm-protols.h     外部函数声明 
                        config/tm.c             诸多的C函数用于 
						支持机器描述 
			

我不知道config/tm.*这四个文件应该扔在哪里,当我们了解交叉编译器后就会发 现把它们放在末尾就好像把足球上的后腰扔在了中卫的位置上。它们的作用就像球 队的后腰,进可攻,退可守。不但直接导致RTL的生成,而且还实现机器汇编代码 转换。这将是我们此次游览的重点之一。

好了,把你们手中的地图扔一边儿去,让我抽根烟歇活儿,这司机开得还真是那 个慢啊!

5.1.3  三

你想摔死我,进站也不用踩急刹车啊!换档知道不?各位,我们已经到站了,请 没有因急刹车而受伤的朋友排队下车。那位哈喇子流一身的男同志,别睡了!

这就是CC1的前端公园,在进入CC1的前端以前,我想我有必要先向各位介绍一下交 叉编译器。所谓交叉编译器(cross compiler)就是在一个平台上编译生成另一个 平台的代码。这里的“平台”,实际上包含两个概念:体系结构(Architecture) 、操作系统(Operating System)。我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的综合;而x86 Win2K平台则是Intel x86体 系结构和Windows 2K for x86操作系统。平台的描述一般采用“CPU-制造厂商-操 作系统”的形式,例如SPARC-SUN-SUNOS,在GCC源代码包的configure.in文件能找 到。那位小姐问的好,我们要交叉编译器来干吗?存在就是合理!在很多嵌入式系 统中,硬件资源十分贫乏,可能连嵌入式操作系统都没有,又怎么能奢望在这块“ 贫瘠的土地”上运行编译器呢?

大家到那边的GNU专卖店购买一个GCC-3.2.2源代码包,凭你的旅游证可以打八折。 让我们在源代码的基础上看看交叉编译器到底是个什么玩意儿。

我们有必要做如下假设:

fengdada(为了纪念本人的师兄)是LD公司的芯片部门职员,他的老板Donald( *_*)是一个想法奇特并且要求古怪的领导。某天,领导Donald命令fengdada开发一 块名为ShortWoman的芯片,具有如下特征:

        1.采用MIPS的体系结构; 
        2.指令集为MIPS指令集的子集; 
        3.不支持浮点; 
        4.没有debug功能; 
        5.只支持st IMM[REG],[REG]或者ld IMM[REG],[REG]的寻址方式; 
	

以后的介绍都将在这块芯片上展开。 郁闷的fengdada在几个通宵之后完成了这个任务(神仙?),并将GCC移植到了 这块新生的芯片上。那么GCC for Shortwoman是怎么工作的呢?我们知道普通主机 编译器的工作方式:

                    native 
        |----------| gcc |--------------| run |------| 
        |   C 代码 |---->|x86可执行程序 |---->| 结果 | 
        |----------|     |--------------|     |------| 
        |                   Linux - X86              | 
        |--------------------------------------------| 
	

在PC机上非常愉快的用C编写程序,调用gcc编译生成可执行文件并且运行。所有的 动作都是在同一个平台Linux-X86上完成。而对于ShortWoman这样一块功能简单的 芯片,只能通过交叉编译器进行开发。

                    cross 
        |----------| gcc |--------------| run |------| 
        |   C 代码 |---->| StWmn代码    |---->| 结果 | 
        |----------|     |--------------|     |------| 
        |       Linux - X86             | ShortWoman | 
        |-------------------------------|------------| 
	

我们一如既往地写完了C代码,此时必须使用对应地交叉编译器生成ShortWoman 的机器码。好了,此时已经不管Linux-X86什么事儿了。将这些生成的程序烧录到 ShortWoman的开发板上去运行,就能得出想要的结果。当然,前提是程序写的准确 无误。在没有调试的情况下就能开发出正确的程序,这样的人和看到苹果落地的牛 顿一样神奇。前面说过,fengdada开发的这块芯片上没有debug功能。这对于 ShortWoman的开发人员来说无异于晴天霹雳。他们只有两个选择:1.每次写完程序 后就开始祷告;2.求助于模拟器SPIM(倒着念试试)。让他们去祷告吧,我们继续 自己的GCC之旅。

    GCC一般有如下参数属性: 
        host        GCC编译后将安装到的机器 
        build       正在编译安装的机器 
        target      GCC建立后所要生成的目标机 
        prefix      和别的prefix作用相同,建议在编译的时候 
                    将编译生成的文件放在别的目录下 
        language    前端支持的高级语言 
	

在configure的时候,它们都采用-host=/-build=/-target=的格式。如果将这 些参数全部忽略,那么,编译后生成的GCC将是普通的支持C和C++的主机(native )编译器。如果host与build相同,而target不同,则叫做交叉(cross编译器。 倘若三者很有个性,彼此相异的话,则是加拿大(Canadian,起源于加拿大三权分 立的政治体制,Stallman这哥儿们真有意思)编译器。一般来说,host和build都 会相同,我可买不起一台SUN的小型机,所以无法验证那些host与build相异的情况 。我们可以画这样一张表来总结一下:

    |-----------------------------------------------| 
    |  名称     |   host    |   build   |   target  | 
    |-----------------------------------------------| 
    |   native  |     O     |     O     |     O     | 
    |-----------------------------------------------| 
    |   cross   |     O     |     O     |     X     | 
    |-----------------------------------------------| 
    |   Canadian|     O     |     X     |     V     | 
    |-----------------------------------------------| 
    

(其他的各种组合是在过于稀少,所以在此略去!)

好了,让我们简单的构建一个mips的交叉编译器吧!用root登陆进行平常的操作 是一个非常糟糕的习惯,很容易在无意中将一些系统文件更改或者删除,所以我建 议大家在编译GCC的时候使用普通帐号。

    mv gcc-3.2.2 gcc-mips 
    mkdir gcc-mips_install 
    cd gcc-mips_install 
    ../gcc-mips/configure --enabel-languages=c --target=mips 
--prefix=$PWD 
    make 
    狂读硬盘二十分钟... 

好累啊,终于完成了。现在用C写个小程序hello.c,gcc-mips_install/gcc/cc1 -mcpu=mips hello.c -o hello.s,看看发生了什么。值得一提的是,在编译GCC这 样的大程序时,Athlon明显比同频率的Pentium芯片要慢,甚至比不上赛杨芯片。 可能是因为Intel的芯片对编译器的支持比较好。不知道为什么,gcc-3. 3.*以上版本中都不再提供-mcpu参数,当然,如果你觉得不爽,可以自己加进去。

好了,让我们马上去参观GCC的前端公园。