The Linux GCC HOWTO中译版V0.2 作者: Daniel Barlow 译者: 陈建勋(Frank J.S. Chen) v1.17, 28 February 1996 _________________________________________________________________ 本文阐述安装GNU C编译器和程式馆的方法,同时概观地说明程式的编译、连结、 执行、除错的过程以及可能面临的诸多问题。写作的参考资料泰半来自於Mitch D'Souza先生所收集的GCC-FAQ;而另一个来源是ELF-HOWTO。此份HOWTO可以说已 代替了GCC-FAQ,而且即将要永久替代ELF-HOWTO了。此乃GCC-HOWTO第一份公开发 行的版本(不须理会版本序号;那是RCS的杰作),有任何指正与建议的,本人都 很欢迎。 _________________________________________________________________ 1. 行远必自迩! * 1.1 译者的话 * 1.2 动与静 * 1.3 作者的私语 * 1.4 印刷与排版 2. 东东在哪儿? * 2.1 GCC-HOWTO在哪儿? * 2.2 GCC相关的资料又在哪儿? * 2.3 GCC * 2.4 C程式馆与标头档 * 2.5 有关联的工具 (as, ld, ar, strings etc) 3. GCC的安装与GCC的设定 * 3.1 GCC的版本 * 3.2 东东装好後都到哪儿去了? * 3.3 标头档ㄋㄟ?标头档ㄋㄟ? * 3.4 建立交叉编译器(Building cross compilers) 4. 移植程式与编译程式 * 4.1 gcc自行定义的符号 * 4.2 线上求助说明 * 4.3 移植能力 5. 除错与监管 * 5.1 预防重於治疗(lint) * 5.2 除错 * 5.3 监管 6. 连结 * 6.1 共享程式库 vs静态程式库 * 6.2 终极审判(‘sin() 在哪个程式库里?’) * 6.3 X档案? * 6.4 建立你自己的程式库 7. 动态载入 * 7.1 基本概念 * 7.2 错误讯息 * 7.3 控制动态载入器的运作 * 7.4 以动态载入撰写程式 8. 与发展人士联络 * 8.1 Bug报表 * 8.2 协助发展 9. 结语 * 9.1 名人榜 * 9.2 翻译 * 9.3 欢迎任何的回馈 * 9.4 合法的行迳规定 10. 索引 _________________________________________________________________ 1. 行远必自迩! 1.1 译者的话 * 这份译文为Linux document projects(LDP)中文翻译计画系列之一。目前之 网址为 [1]http://www.linux.org.tw/CLDP/,欢迎各位网友踊跃投入此一计 画。 * 我并没有完全按照原文逐字翻译。为了力求译文通畅可读,我会稍稍的重组 一部份的文字,加油添醋,或是精简原文;这样做的话,可以弥补中英文间 语法结构的差异性,且语气可以贯通无碍。 * 一些关键字与专业词汇等,会附加上原文单字。 * 遇有转译困难,唯原文常见的字汇如bugs、shadow password、padding 、image之类的,则保留原文不变。若阁下对这些字汇有适当译辞的,请不吝 指教。 * 内文中若遇有"[译者注:**]"之标记,则为本人额外之注解。 * 对这篇译文有任何建议与疑问的,请email至frank63@ms5.hinet.net。 * WWW Home Page: [2]http://linux.ntcic.edu.tw/~jsfrank/。 * 此中译文件之翻译权已取得英籍之原作者Daniel Barlow 先生之同意;另, 陈建勋先生保有此份中译版文件所有的权利,你可以任意的拷贝,以各种媒 体散布这份中译文件,唯此节补充说明需原封不动附上,且不可任意更动译 文。 * v0.1版的译文相当粗糙,连文句的语法结构都嫌太过於松散,v0.2版针对中 文的用字习惯来修正,并将上一版译的不妥当的地方修正过来,例如either 这个字,英国人常把either当名词用,指两者中任意一个;这份HOWTO就充份 反应出这个用字习性,跟美语有基本上的差异。 * 文中有几句话没有译出来,一方面是看不懂,另方面是直译也译不出来,所 以只好保留原文了,要是阁下有新的领悟的,无论如何请告诉我。 * v0.1版翻译起始日期为:11/7/97;截止日期为:11/19/97。 * v0.2版修正起始日期:5/13/98;截止日期为:6/3/98。 1.2 动与静 目前Linux的发展正波涛汹涌的进行著。简单一点讲,Linux有两种执行档的格式 可用,取决於你的系统是怎麽整合起来的;你的Linux应该是其中一种吧!阅读这 份文件,可以帮助你□清执行档的类别。 要如何区别呢?执行公用程式‘file’(例如,file /bin/bash)就对了。 就ELF的程式而言,萤幕上显示出来的讯息会含有ELF的字眼;如果说是a.out的, 讯息内会箝有 Linux/i386的字样。 ELF与a.out格式的差异之处会在後续的章节中讨论(很广泛喔!)。ELF是比较新 的格式,一般而言,接受的程度较佳。 1.3 作者的私语 版权说明与合法的行迳规定,就摆在这份文件的尾端。除此之外,我还有一些不 得不提醒你的话要讲。就算你□著没事干,也不要在Usenet上丢一些呆瓜问的问 题;还有,不要老以为自己C语言的功力深厚,专门发表一些不是bugs的bugs出来 丢人现眼,这不就等於告诉别人你不学无术,在关老爷面前耍大刀了吗?所以说 自以为是的英雄主义是得不偿失的。 1.4 印刷与排版 如果你现在读的是Postscript、dvi或是html格式,那麽你所看到的字型变化就会 比只读纯文字格式的人多一些。特别是档案名称、命令、命令的输出与摘录出来 的原始码等,统统都是打字机的字型。这样做的话,对於某些需要强调的‘变数 ’还有那些没有固定结果的□例而言,就可以达到强调的效果了。 读这份文件的同时,你也会得到一套蛮有用的索引。假若是dvi、 postscript之 类的版本,索引的数字就是章节(section)的编号;如果是HTML的话,这些数字会 按顺序排列,你可以用滑鼠左键去连结相对的索引内容;如果你看的是纯文字版 本的话, 数字就只是数字,没别的含意;建议你赶快升级为妙哩! 我用的shell是Bourne shell(不是C shell),举的例子自然是Bourne shell的 语法。如果你用的是C shell的话,设定环境变数的语法会像下面这样: % setenv FOO bar 要是用Bourne shell的话,我会这样子写: $ FOO=bar; export FOO 如果提示符号显示的是井字符号#而不是钱字符号 $的话,很有可能这个命令是只 适用於root而已。当然啦,要是你试了这些□例,结果弄得你的系统发生灾变, 我可是一点责任也不会负的喔!祝好运!:-) 11/8/97译. 5/13/98修订 2. 东东在哪儿? 2.1 GCC-HOWTO在哪儿? 这份文件是Linux HOWTO系列之一,换句话说,你可以在所有存放Linux HOWTO文 件的网站上面找到她的芳踪,例如 [3]http://sunsite.unc.edu/pub/linux/docs/HOWTO/。HTML的版本(可能会是较 新的版本)可以从 [4]http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html上面抓下来。 2.2 GCC相关的资料又在哪儿? 标准的gcc说明文件是随附在发行的原始码(source distribution)内(往下看就 有了!),里头有textinfo与.info两种档案。要是你的网路连接速率够快,或是 有一片cdrom,不然,有高度的耐心也成,你可以自己把它untar,然後再把相对 应的位元一一拷贝到/usr/info的目录底下。假如你的条件与上述的不符,不妨到 [5]tsx-11站上去参观参观。不过,我想,没有必要老是惦记著最新的版本吧! libc的文件说明有两种来源。一种是GNU libc,以.info的格式储存,除了stdio 之外,其馀Linux libc的说明都相当的详尽精确。另一种可以在Linux的archive [6]manpages上找到系统呼叫(system call)(第2节)与libc函数(function) (第3节)的文件说明。 2.3 GCC 解答有二: (a)你可以在 [7]ftp://tsx-11.mit.edu:/pub/linux/packages/GCC/的网站上找 到 正式的Linux GCC发行系统(distribution),而且是已经编译好的可执行档。 当我在写这份文件时,2.7.2(gcc-2.7.2.bin.tar.gz)是最新的版本。 (b)自由软体基金会(Free Software Foundation)所发布的GCC最新原始码可以 从网站 [8]GNU archives上取得。没有必要非得与上述的版本一致才行,不过这 个版本的确是目前最新的。Linux GCC的维护网友(maintainers)让你可以很轻松 的自行编译这个最新的版本。configure命令稿(script)会帮你自动设定好所有该 做的事情。建议你有空不妨到 [9]tsx-11看看,说不定会有修正的版本是你会想 要用的。 如果想要编写出一些有用的软体(不是我罗唆,还是有不少没啥用途的软体在网 路上四处流窜。),下面这一小节所谈的也是你需要的: 2.4 C程式馆与标头档 该选哪一套程式馆是取决於(i)你的系统是ELF的或是a.out的;(ii)你希望你的系 统变成哪一种?如果你是从libc 4升级到libc 5,那麽给你一个良心的建议,先 去看看ELF-HOWTO再说。你一定会问,在ELF文件的哪儿呢?嘿!嘿!不偏不倚, 就差不多跟这份文件相同的位置。网站 [10]tsx-11上面可以找到你想要的。 libc-5.2.18.bin.tar.gz --- ELF共享程式馆(ELF shared library images),静态程式馆 (static libraries)与标头档(针对C语言与数学程式馆)。 libc-5.2.18.tar.gz ---libc-5.2.18.bin.tar.gz的原始码。这个档案你也需要,因为.bin.套 件(package)含有必需的标头档。如果此时你正犹豫不决,不晓得是老身 亲自下海,动手编译C程式库比较好;还是直接用人家编译好的二进位 档(binaries)就可以了。有这种困扰的人,来,看我的嘴形:用人家编译 好的二进位档不就解决了嘛!只有在你想要NYS或是shadow password的情 况下,才需要自己的手来推动摇篮。 libc-4.7.5.bin.tar.gz --- 这个档案是a.out的共享程式库(shared library images)与静态程式 库,用途是为了与前述的libc 5套件共存共荣而设计的,不过除非你想要 继续使用a.out的程式或是继续发展a.out的程式,不然的话,是不需要它 的。 2.5 有关联的工具 (as, ld, ar, strings etc) 到目前为止,与之前所谈的都一样,从网站 [11]tsx-11上,就可以找到这些工具 程式。目前的版本是binutils-2.6.0.2.bin.tar.gz。 需要注意的是binutils只适用於ELF,而且目前libc的版本也都是属於ELF的;当 然啦,习惯a.out的人如果有个ELF的libc与a.out的libc联合起来一起使用,这对 他们来讲是再好不过的美事了。不可否认的,C程式馆的发展正以坚决的脚步迈 向ELF的格式,除非你真的有很好的理由,需要a.out的东东,不然啊,大家都会 鼓励你勇於突破,趁早加入锐不可挡的大潮流。 11/9/97译 3. GCC的安装与GCC的设定 3.1 GCC的版本 在shell的提示符号下键入gcc -v,萤幕上就会显示出你目前正在使用的GCC的版 本。同时这也是一个相当可靠的方法,可以确定你现在所用的是ELF或是a.out。 在我的系统上,执行gcc -v的结果是: $ gcc -v Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs gcc version 2.7.2 上面的讯息指出了几件重要的事情: * i486 这是指明你现在正在用的gcc是为了486的微处理器而写的---你的电脑 可能是386或是586。这3种微处理器的晶片所编译而成的程式码,彼此间是可 以相容使用的。差别之处是486的程式码在某些地方有加上padding的功能, 所以可以在486上面跑得比较快。这对386的机器而言,执行程式的效能并不 会有什麽不良的影响,只不过真的会让程式码变得稍稍的大了一些。 * box 这可以说一点也不重要;不过也可能另有所指(像是slackware或者 是debian),或者根本什麽也不是(所以罗!完整的目录名称是i486-linux )。假如你是实务派的佼佼者,亲自动手建立属於自己的gcc,那麽你可以在 建立的过程中设定这一项,以装点门面。就像我做的一样:-)。 * linux 其实这是指linuxelf或是linuxaout。这一项会令人引起不必要的困惑 ,究竟是指哪一种会根据你所用的版本而异。 + linux 意指ELF若版本序号是2.7.0.(或是更新的版本);否则的话, 就是a.out的了。 + linuxaout 意指a.out的格式。当linux的定义从a.out更换到ELF时 ,linuxaout就会顺水推舟,摇身一变,成了一个目标物。因此,你不 会看到任何版本新於2.7.0.的gcc有linuxaout的。 + linuxelf 已经过时了。通常这是指2.6.3版的gcc,而且这个版本也可 以用来产生ELF的可执行档。要注意的是,gcc 2.6.3版在产生ELF程式 码时会有bugs,所以如果你目前用的恰好是这个版本,建议你赶快升级 。 * 2.7.2 版本的序号。 所以,总结起来,我有2.7.2版的gcc,可以产生ELF格式的程式码。就这麽简单, 惊讶吧!eh? 3.2 东东装好後都到哪儿去了? 如果安装gcc时没有仔细的看著萤幕,或者你是从一个完整的发行系统里把gcc单 独抓出来安装的话,那麽也许你会想知道到底这些东东装好後是住在整个档案系 统的那些地方。几个重点如下: * /usr/lib/gcc-lib/target/version/ (与子目录)大部份的编译器就是住在 这个地方的。在这儿有可执行的程式,实际在做编译的工作;另外,还有一 些特定版本的程式库与标头档等也会储存在此。 * /usr/bin/gcc 指的是编译器的驱动程式---也就是你实际在命令列(command line)上执行的程式。这个目录可供各种版本的gcc使用,只要你用不同的编 译器目录(如上所述)来安装就可以了。要知道内定的版本是那一个, 在shell提示符号下打gcc -v。要是想强迫执行某个版本,就换打gcc -V version。例如: # gcc -v Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs gcc version 2.7.2 # gcc -V 2.6.3 -v Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.6.3/specs gcc driver version 2.7.2 executing gcc version 2.6.3 * /usr/target/(bin|lib|include)/ 如果你装了数种的目标物件,例如a.out 与elf,或是某一种的交叉编译器(cross-compiler)等等,那些属於非主流目 标物件(non-native target(s))的程式库,binutils(as、ld等等)工具 与标头档等都可以在这儿找到。即使你只安装了一种gcc,还是可以在这儿找 到这些原本就是替它们准备的东东。如果不是在这儿,那麽就应该是 在/usr/(bin|lib|include)了。 * /lib/,/usr/lib 与其它的目录等都是主流系统(native-system)的程式馆 目录。许多的应用程式都会用到/lib/cpp,因此你也需要它---作法上,不是 从/usr/lib/gcc-lib/target/version/ 目录里拷贝,就是弄个符号连结 (symlink)指向那儿。 [译者注:所谓的native,是指目前你的系统是 以a.out或elf的格式为主,或者内定的gcc是哪一种版本等等。native的意思 是‘本土的’、‘本国的’与‘天生的’……等等;当你拿到一片CD-ROM重 头至尾将Linux安装完成,让Linux出生,成为你个人特色浓厚的作业平台後 ,如果再加装一些不一样的目标物件,自然就有‘本土’与‘外省’( 无关 政治),‘本国’与‘外国’、‘天生’与‘人为’等等的区别,同时也含 有内定(default)的意思在。假若再附加上你个人的价值观判断和喜好,我 想用主流(native)与非主流(non-native)来翻译应该还算恰当。] 3.3 标头档ㄋㄟ?标头档ㄋㄟ? 假如把你自行安装在/usr/local/include目录底下的标头档排除在外的话 ,Linux还有另外3种主要的标头档: * /usr/include/与其子目录底下的标头档,大部份都是由H.J.Lu发展的libc套 件(libc binary package)所提供的。我会只说‘大部份’的原因,是因为你 可能有其它来源的标头档(像是curses与dbm程式库等等)摆在这儿;尤其是 ,如果你现在用的是最新的libc发行系统的话(新版本不像旧版那样,已经 不再支援curses或dbm了。),那东东之多是人人为之咋舌的! [译者 注:libc binary package意指以二进位形式(machine code)储存之套件,并 非原始码(text),若要以中文全称译出,则成‘libc二进位档套件’,似 有聱牙之嫌,故略去binary,以libc套件通称。] * 在核心原始码的发行系统内(kernel source distribution) ,/usr/include/linux 与 /usr/include/asm (里头有这些档案 :)应该有符号连结(symbolic links)可以连 结至目录linux/include/linux 与 linux/include/asm。如果你有鸿鹄之志 的话,安装这些东东後,就不应该只是拿来编译核心(kernel)而已。 把原 始码解压缩(unpacking)後,可能你也会发现,需要在核心的目录 (kernel directory)底下做make config的动作。很多的档案都会依 赖的帮忙,可是这个档案却有可能因版本不同而不存在 。若干核心版本里,asm就只是它自己的一个符号连结,仅仅是在make config时才建立出来而已。 [译者注:原文提及autoconf.h时是‘Many files depend on ,which otherwise may not exist,* ’。此处之otherwise之词性应为形容词(adj),指‘另一情况’、‘另一种 ’、‘不同的’之意,将原文形容词子句拆开来应为:(i) Many files depend on . (ii) of other condition may not exist. 与下一句互相比对,此处应同指在不同版本之情 况下。] 所以,当你在目录/usr/src/linux底下,解开核心的程式码时,就 照著下面指示的做吧! $ cd /usr/src/linux $ su # make config (回答接下来的问题。通常回答得正不正确并不重要,除非你打算继续□造核心。) # cd /usr/include # ln -s ../src/linux/include/linux . # ln -s ../src/linux/include/asm . * 诸如之类 的档案,会随著不同的编译器版本而异,属於你‘个人的’档案,可以在 /usr/lib/gcc-lib/i486-box-linux/2.7.2/include/与其它相类似(相同) 的目录名称的地方找到。 11/11/97译 5/14/98修正 3.4 建立交叉编译器(Building cross compilers) 将Linux当作标的作业平台(target platform) 假设你已经拿到gcc的原始码,通常你只要依循INSTALL档的指示便可完成一切的 设定。 make後面再接configure --target=i486-linux --host=XXX on platform XXX,就能帮你变把戏了。要注意的是,你会需要Linux还有核心的标头 档;同时也需要建立交叉组译器(cross assembler)与交叉连结器(cross linker),来源是 [12]ftp://tsx-11.mit.edu/pub/linux/packages/GCC/。 Linux当成原始作业平台(source platform)而MSDOS作为标的作业平台 Ugh。很明显的,这个大概需要用到套件“emx”或是延伸套件“go”。请自行去 [13]ftp://sunsite.unc.edu/pub/Linux/devel/msdos看看。我并没有测试过这些 个东西,所以没有办法保证什麽。 4. 移植程式与编译程式 4.1 gcc自行定义的符号 只要执行gcc时,附加 -v这个参数,就能找出你所用的这版gcc,自动帮你定义了 什麽符号。例如,我的机器看起来会像这样: $ echo 'main(){printf("hello world\n");}' | gcc -E -v - Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs gcc version 2.7.2 /usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386) -Amachine(i386) -D__i486__ - 假若你正在写的程式码会用到一些Linux独有的特性,那麽把那些无法移植的程式 码,以条件式编译的前置命令封括起来,可是个不错的主意呢!如下所示∶ #ifdef __linux__ /* ... funky stuff ... */ #endif /* linux */ 用__linux__就可以达成目的;看仔细一点,不是linux喔。尽管linux也有定义, 毕竟,这个仍然不是POSIX的标准。 4.2 线上求助说明 gcc编译器参数的说明文件是gcc info page(在Emacs内,按下C-h i,然後选 ‘gcc’的选项)。要是弄不出来,不是卖你CD-ROM的人没把这个东东压给你,不 然就是你现在用的是旧版的。遇到这种情况,最好的方法是移动尊臀到archive [14]ftp://prep.ai.mit.edu/pub/gnu或是它的mirrors站台,去把gcc的原始档案 抓回家,重新烹饪一番。 gcc manual page(gcc.1) 可以说是已经过时了,要是你吃饱了撑著没事干硬是 想看,它就会告诉你说别无聊了。 旗正飘飘 在命令列上执行gcc时,只要在它的屁股後面加上-On的选项,就能让gcc乖乖的替 你生出最佳编码的机器码。这里的n是一个可有可无的小整数,不同版本的gcc ,n的意义与其正确的功效都不一样,不过,典型的□围是从0(不要鸡婆,我不 要最佳编码。)变化到2(最佳编码要多一点。),再升级到3(最佳编码要再多 一点,多一点)。 gcc在其内部会将这些数字转译成一系列的-f与-m的选项。执行gcc时带上旗号-v 与-Q,你就能很清楚的看出每一种等级的-O是对应到那些选项。好比说,就-O2来 讲,我的gcc告诉会我说: enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks -fexpensive-optimizations -fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline -fcaller-saves -fpcc-struct-return -frerun-cse-after-loop -fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float -mno-386 -m486 -mieee-fp -mfp-ret-in-387 要是你用的最佳编码等级高於你的编译器所能支援的(e.g. -O6),那麽它的效 果就跟你用你的编译器所能提供的最高等级的效果是一样的。说实在的,发行出 去的gcc程式码,用在编译时竟是如此处理这等问题,真的不是什麽好的构想。日 後若是有更进步的最佳编码方法具体整合到新的版本里,而你(或是你的users) 还是试著这样做的话,可能就会发现gcc会中断你的程式了。 从gcc 2.7.0升级到2.7.2的users应该注意一点,使用-O2时会有一个bug。更糟糕 的是,强度折减参数(strength reduction)居然没有用!要是你喜欢重新编 译gcc的话,是有那麽一个修正的版本可以更正这项错误;不然的话,一定要确定 每次编译时都有加上-fno-strength-reduce喔! 11/12/97译 有个性的微处理器 有一些-m的旗号十分有用处,但是却无法藉由各种等级的-O打开来使用。这之中 最重要的有是-m386和-m486这两种,用来告诉gcc该把正在编译的程式码视作专 为386或是486机器所写的。不论是用哪一种-m来编译程式码,都可以在彼此的机 器上执行,-m486编译出来的码会比较大,不过拿来在386的机器上跑也不会比较 慢就是了。 目前尚无-mpentium或是-m586的旗号。Linus建议我们可以用-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2来得到最佳编码的486 程式码,这样做正好就可以避免alignment(Pentium并不需要)有过大的gaps发 生。Michael Meissner说: 我的第六感告诉我,-mno-strength-reduce(嘿!要晓得我可不是在谈强度折 减参数的bug呀,那已经是另外一个争论的战场了。)一样也可以在x86的机器 上产生较快的程式码,这是因为x86的机器对暂存器有著不可磨灭的□渴在, 而且GCC's method of grouping registers into spill registers vs. other registers doesn't help either。传统上,强度折减的结果会使得编 译器去利用加法暂存器以加法运算来取代乘法运算。事实上,我在怀 疑-fcaller-saves可能也只是个漏洞也说不定。 而我的第七感则再度的告诉我说,-fomit-frame-pointer可能会也可能不会有 任何的赚头。从这点来看,就是意谓著有另一个暂存器可以用来处理记忆体分 配的问题。另方面,若纯粹从x86的机器在转换它的指令集成为机器码的方法 上来看,便意谓著堆叠所用到的记忆体空间要比frame所用到的还要来得多; 换句话说,Icache对程式码而言并没有实质上的帮助,若是阁下用 了-fomit-frame-pointer的话,同时也是告诉编译器在每次呼叫函数之後,就 必须修正堆叠的指标;然而,就frame来讲,若呼叫的次数不多的话,则允许 堆叠暂时堆积起来。 有关这方面主题的最後一段话仍是来自於Linus: 要注意的是,如果你想要得到最佳状况的执行效能,可千万别相信我的话。无 论如何,一定要进行测试。gcc编译器还有许多的参数可用,其中可能就有一 种最特别的组合,可以给你最佳编码的结果。 11/14/97译 5/15/98修正 Internal compiler error: cc1 got fatal signal 11 Signal 11是指 SIGSEGV,或者 ‘segmentation violation’。通常这是指 说gcc对自己所用的指标感到困惑,而且还尝试著把资料写入不属於它的记忆体里 。所以,这可能是一个gcc的bug。 然而,大体而言,gcc是一支经过严密测试且 可靠度良好的软体佳作。它也用了大量复杂的资料结构与惊人的指标数量。简言 之,若是要评选本世纪最挑惕与最一丝不□的RAM测试程式,gcc绝对可以一摘后 冠。假如你无法重新复制这只bug---当你重新开始编译时,错误的讯息并没有一 直出现在同一个地方---那几乎可以确定,是你的硬体本身有问题(CPU,记忆体,主 机板或是快取记忆体).千万不要因为你的电脑可以通过开机程序的测试、或 是Windows可以跑得很顺、或者其它什麽的,就回过头来大肆宣传说这是gcc的一 个bug;你所做的这些测试动作,通常没有什麽实际上的价值,这是很合理的结论 。另外,也不要因为编译核心时,总是停留在‘make zImage’的阶段,就要大骂 这是gcc的bug---当然它会停在那儿啊!做‘make zImage’时,需要编译的档案 可能就超过200档案;我们正在研拟一个替代的方案。 如果你可以重覆产生这个bug,而且(最好是这样啦!)可以写一个短小的程式来 展示这只bug的话,你就可以把它做成bug报告,然後email给FSF,或者 是linux-gcc通信论坛。你可以去参考gcc的说明文件,看看有什麽详细的资讯,是 他们所需要的。 4.3 移植能力 据报,近日来许多正面的消息指出,若是有某件东东到现在都还没移植到Linux上 去,那麽可以肯定的是,它一定一点价值也没有。:-) 嗯!正经一点。一般而言,原始码只需要做一些局部的修改,就可以克服Linux 100%与POSIX相容的特质。如果你做了任何的修改,而将此部份传回给原作者,会 是很有建设性的举动。这样日後就只需要用到‘make’,就能得到一个可执行的 档案了。 BSD教徒 (有 bsd_ioctl、daemon 与 ) 编译程式时,可以配合-I/usr/include/bsd与连结-lbsd的程式库。(例如:在你 的Makefile档内,把-I/usr/include/bsd加到CFLAGS那一行;把-lbsd加 到LDFLAGS那一行)。如果你真的那麽想要BSD型态的信号行为,也不需要再加 上-D__USE_BSD_SIGNAL了。那是因为当你用了-I/usr/include/bsd与含括了标头 档之後,make时就会自动加入了。 失落的封印(SIGBUS, SIGEMT, SIGIOT, SIGTRAP, SIGSYS etc) Linux与POSIX是完全相容的。不过,有些信号并不是POSIX定义的---ISO/IEC 9945-1:1990 (IEEE Std 1003.1-1990), paragraph B.3.3.1.1 sez: “在POSIX.1中省略了SIGBUS、SIGEMT、SIGIOT、SIGTRAP与SIGSYS信号,那是 因为它们的行为与实作的方式息息相关,而且也无法进行适当的分类。确认实 作方式後,便可以发送这些信号,可是必须以文件说明它们是在什麽样的环境 底下发送出来的,以及指出任何与它们的发展相关的限制。” 想要修正这个问题,最简单也是最笨的方法就是用SIGUNUSED重新定义这些信号。 正确的方法应该是以条件式的编译#ifdef来处理这些问题才对: #ifdef SIGSYS /* ... non-posix SIGSYS code here .... */ #endif 11/15/97译 5/22/98修正 K & R gcc是一个与ANSI相容的编译器;奇怪的是,目前大多数的程式码都不符合ANSI所 定的标准。如果你热爱ANSI,喜欢用ANSI提供的标准来撰写C程式,似乎除了加 上-traditional的旗号之外,就没有其它什麽可以多谈的了。There is a certain amount of finer-grained control over which varieties of brain damage to emulate;请自行查阅gcc info page。 要注意的是,尽管你用了-traditional来改变语言的特性,它的效果也仅局限 於gcc所能够接受的□围。例如, -traditional会打开-fwritable-strings,使得 字串常数移至资料记忆体空间内(从程式码记忆体空间移出来,这个地方是不能任 意写入的)。这样做会让程式码的记忆体空间无形中增加的。 前置处理器的符号卯上函数原型宣告 最常见的问题是,如众所皆知,Linux中有许多常用的函数都定义成巨集存放在标 头档内,此时若有相似的函数原型宣告出现在程式码内,前置处理器会拒绝进行 语法分析的前置作业。常见的有atoi()与atol()。 sprintf() 在大部份的Unix系统上,sprintf(string, fmt, ...)传回的是string的指标,然 而,这方面Linux(遵循ANSI)传回的却是放入string内的字元数目.进行移植时 ,尤其是针对SunOS,需有警觉的心。 fcntl 与相关的函数;FD_*家族的定义到底摆在哪里? 就在里头。 为了真正的原型宣告,当你用了fcntl,可能你也想含 括标头档进来。 一般而言,函数的manual page会在SYNOPSIS章节内列出需要的标头档。 select()的计时---程式执行时会处於忙碌-等待的状态 很久很久以前,,select()的计时参数只有唯读的性而已。即使到了最近 ,manual pages仍然有下面这段的警告: select()应该是藉由修正时间的数值(如果有的话),再传回自原始计时开始 後所剩馀的时间。未来的版本可能会使这项功能实现。因此,就目前而言,若 以为呼叫select()之後,计时指标仍然不会被修正过,可是一种非常不明智的 想法喔! 未来就在我们的眼前了!至少,在这儿你绝对可以看到。函数select()传回的, 是扣除等待尚未到达的资料所耗费的时间後,其剩馀的时间数值。如果在计时结 束时,都没有资料传送进来,计时引数便会设为0;如果接著还有任何 的select(),以同样的计时structure来呼叫,那麽select()便会立刻结束。 若要修正这项问题,只要每次呼叫select()前,都把计时数值放到计时 structure内,就没有问题了。把下面的程式码, struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; while (some_condition) select(n,readfds,writefds,exceptfds,&timeout); 改成, struct timeval timeout; while (some_condition) { timeout.tv_sec = 1; timeout.tv_usec = 0; select(n,readfds,writefds,exceptfds,&timeout); } 这个问题,在有些版本的Mosaic里是相当著名的,只要一次的等待,Mosaic就挂 在那里了。Mosaic的萤幕右上角,是不是有个圆圆的、会旋转的地球动画。那颗 球转得愈快,就表示资料从网路上传送过来的速率愈慢! 产生中断的系统呼叫 特徵: 当一支程式以Ctrl-Z中止、然後再重新执行时□或者是其它可以产生Ctrl-C中断 信号的情况,如子程序的终结等□系统就会抱怨说"interrupted system call"或 是"write: unknown error",或者诸如此类的讯息。 问题点: POSIX的系统检查信号的次数,比起一些旧版的Unix是要多那麽一点。如果 是Linux,可能就会执行signal handlers了□ * 非同步地(计时器的滴答声) * 系统呼叫的传回值 * 在下列系统呼叫的执行期间∶ select(), pause(), connect(),accept(), read() on terminals, sockets, pipes or files in /proc, write() on terminals, sockets, pipes or the line printer, open() on FIFOs, PTYs or serial lines,ioctl() on terminals, fcntl() with command F_SETLKW, wait4(), syslog(), any TCP or NFS operations. 就其它的作业系统而言,你需要的可能就是下面这些系统呼叫了: creat(), close(), getmsg(), putmsg(), msgrcv(), msgsnd(), recv(), send(), wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop() to this list. 在系统呼叫期间,若有一信号(那支程式本身应准备好handler因应了)产生 ,handler就会被呼叫。当handler将控制权转移回系统呼叫时,它会侦测出它已 经产生中断,而且传回值会立刻设定成-1,而errno设定成EINTR。程式并没有想 到会发生这种事,所以就挂了。 有两种修正的方法可以选择: (1) 对每个你自行安装的signal handler,都须在sigaction的旗号加 上SA_RESTART。例如,把下列的程式, signal (sig_nr, my_signal_handler); 改成, signal (sig_nr, my_signal_handler); { struct sigaction sa; sigaction (sig_nr, (struct sigaction *)0, &sa); #ifdef SA_RESTART sa.sa_flags |= SA_RESTART; #endif #ifdef SA_INTERRUPT sa.sa_flags &= ~ SA_INTERRUPT; #endif sigaction (sig_nr, &sa, (struct sigaction *)0); } 要注意的是,当这部份的变更大量应用到系统呼叫之後,呼叫read()、write() 、ioctl()、 select()、 pause() 与 connect()时,你仍然得自行检查EINTR。 如下所示: (2) 你自己得很明确地检查EINTR: 这里有两个针对read()与ioctl()的例子。 原始的程式片段,使用read(): int result; while (len > 0) { result = read(fd,buffer,len); if (result < 0) break; buffer += result; len -= result; } 修改成, int result; while (len > 0) { result = read(fd,buffer,len); if (result < 0) { if (errno != EINTR) break; } else { buffer += result; len -= result; } } 原始的程式片段,使用ioctl(): int result; result = ioctl(fd,cmd,addr); 修改成, int result; do { result = ioctl(fd,cmd,addr); } while ((result == -1) && (errno == EINTR)); 注意一点,有些版本的BSD Unix,其内定的行为是重新执行系统呼叫。若要让系 统呼叫中断,得使用 SV_INTERRUPT或SA_INTERRUPT旗号。 可以写入的字串 gcc对其users总怀抱著乐观的想法,相信当他们打算让某个字串当作常数来用 时---那它就真的只是字串常数而已。因此,这种字串常数会储存在程式码的记忆 体区段内。这块区域可以page到磁碟机的image上,避免耗掉swap的记忆体空间, 而且任何尝试写入的举动都会造成分页的错误(segmentation fault)。这可是一 种特色呢! 对老旧一点的程式而言,这可能会产生一个问题。例如,呼叫mktemp(),传递的 引数(arguments)是字串常数。 mktemp()会尝试著在*适当的位置*重新写入它的 引数。 修正的方法不外乎(a)以-fwritable-strings编译,迫使gcc将此常数置放在资料 记忆体空间内;或者(b)将侵犯地权的部份重新改写,配置一个不为常数的字串, 在呼叫前,先以strcpy()将资料拷贝进去。 为什麽呼叫execl()会失败? 那是因为你呼叫的方式不对。execl的第一个引数是你想要执行的程式名.第二个 与接续的引数会变成你所呼叫的程式的argv阵列。记住:传统上,argv[0]是只有 当程式没有带著引数执行时,才会有设定值。所以罗,你应该这样写: execl("/bin/ls","ls",NULL); 而不是只有, execl("/bin/ls", NULL); 执行程式而不带任何引数,可解释成是一种邀请函,目的是把此程式的动态程式 库独立的特性印出来。至少,a.out是这样的。就ELF而言。事情就不是这样了. (如果你想得知此程式库的资讯,有一些更简单的介面可用;参考动态载入那一 章节,或是ldd的manual page。) 11/16/97译 6/2/98修正 5. 除错与监管 5.1 预防重於治疗(lint) lint对Linux而言并没有很广泛的用途,主要是因为大部份的人都能满足於gcc所 提供的警告讯息。可能最有用的就是-Wall参数了---这个参数的用途是要求gcc将 所有的警告讯息显现出来;but probably has more mnemonic value if thought of as the thing you bang your head against. 网路上有一个实用的public domain lint,位於 [15]ftp://larch.lcs.mit.edu/pub/Larch/lclint。我并不知道这个站到底有多 好就是了。 5.2 除错 我要怎样做才能将除错资讯放到一支程式里头? 你需要添加-g的参数来编译与连结程式,而且不可以用-fomit-frame-pointer参 数。事实上,你不需要重新编译所有的程式,只需重新编译目前你正在除错的部 份即可。 就a.out的组态而言,共享程式库是以-fomit-frame-pointer编译而成,这个时候 ,gdb就变得英雄无用武之地了。连结时给定-g的选项,应该就隐含著静态连结的 意义了;这就是为什麽要加-g的原因了。 如果连结器连结失败,告诉你找不到libg.a,那就是在/usr/lib/的目录底下,少 了libg.a。libg.a是一个C语言很特别的侦错程式库。一般在libc的套件内就会提 供libg.a;不然的话(新版是这样的),你可能需要拿libc的原始码自己设置了 ,不过,实际上你应该不需要才对。不管是什麽目的,大部份的情况下,只需 将libg.a连结到/usr/lib/libc.a,你就能得到足够的资讯了。 那,能不能把除错资讯给拿掉? 很多的GNU软体在编译连结时,都会设定-g的选项;这样做会造成执行档过大的问 题(通常是静态的连结)。实际上,这并不是一个很热门的想法。 如果程式本身有autoconf,产生了configure命令稿,通常你就可以 用./configure CFLAGS=或是./configure CFLAGS=-O2来关掉除错资讯。不然的话 ,你得检查检查Makefile了。当然啦,假如你用的是ELF,程式便会以动态的方式 来连结,不论是否有-g的设定;因此你可以平常心把-g拿掉。 实用的软体 据了解,大部份的人都是用gdb来除错。你可以从 [16]GNU archive sites拿到原 始程式;或者是到 [17]tsx-11拿可执行档。xxgdb是一个X介面的除错程式,植基 於gdb(也就是说你得先安装好gdb,才能再装xxgdb)。xxgdb的原始码可以在 [18]ftp://ftp.x.org/contrib/xxgdb-1.08.tar.gz找到。 另外,UPS除错程式已由Rick Sladkey移植成功。UPS可以在X底下活得很好,不 像xxgdb那样---仅仅是gdb的X前端介面(X front end)。这支除错程式有一大堆 优良的特点,而且如果你得花时间去除一支破烂的程式,建议你考虑考虑xxgdb。 事先编译好的Linux版与修正版的原始码可以在 [19]ftp://sunsite.unc.edu/pub/Linux/devel/debuggers/找到。而最初的原始 程式则放在 [20]ftp://ftp.x.org/contrib/ups-2.45.2.tar.Z。 你可能会发现另一个用来除错的工具strace,也是相当的有用。它可以显示出由 程序所产生的系统呼叫,而且还拥有其它众多繁复的功能,像是如果你手边没有 原始码的话,strace可以帮你找出有那些路径名称(path-names)已编译进执行 档内; exacerbating race conditions in programs that you suspect contain them;还有,strace可拿来学习程式是怎麽在电脑中执行的。最新的版本 (目前是3.0.8)可在找到 [21]ftp://ftp.std.com/pub/jrs/。 背景程式(常驻程式) 早期典型的常驻程式(daemon programs)是执行fork(),然後终止父程序。这样的 做法使得除错的时间减短了。 了解这点的最简单的方法就是替fork()设一个中断点(breakpoint)。当程式停 止时,强迫fork()传回0。 (gdb) list 1 #include 2 3 main() 4 { 5 if(fork()==0) printf("child\n"); 6 else printf("parent\n"); 7 } (gdb) break fork Breakpoint 1 at 0x80003b8 (gdb) run Starting program: /home/dan/src/hello/./fork Breakpoint 1 at 0x400177c4 Breakpoint 1, 0x400177c4 in fork () (gdb) return 0 Make selected stack frame return now? (y or n) y #0 0x80004a8 in main () at fork.c:5 5 if(fork()==0) printf("child\n"); (gdb) next Single stepping until exit from function fork, which has no line number information. child 7 } 核心档案 当Linux开机时,通常组态会设定成不要产生核心档案。要是你那麽喜欢它们的话 ,可以用shell的builtin命令使其重新生效:就C-shell相容的shell(如tcsh) 而言,会是下面这样: % limit core unlimited 而类似Bourne shell的shell(sh, bash, zsh, pdksh)则使用下面的语法: $ ulimit -c unlimited 如果你想要有个多才多艺的核心档命名(core file naming)(for example, if you're trying to conduct a post-mortem using a debugger that's buggy itself),那麽你可以对你的核心程式做一点小小的更动。找一 找fs/binfmt_aout.c与fs/binfmt_elf.c档中与下列相符的程式片段(in newer kernels, you'll have to grep around a little in older ones): memcpy(corefile,"core.",5); #if 0 memcpy(corefile+5,current->comm,sizeof(current->comm)); #else corefile[4] = '\0'; #endif 将0换成1. 5.3 监管 监管(Profiling)是用来检核一支程式中那些部份是最常呼叫或是执行的时间最 久的方法。这对程式的最佳化与找出何时时间是浪费掉的而言,是相当好的方式 。你必须就你所要的时程资讯(timing information)的目的档加上-p来编译, 而且如果要让输出的档案有意义,你也会需要gprof(来自binutils套件的命令) 。参阅gprof的manual page,可得知其细节。 11/18/97译 6. 连结 由於静态与共享程式库两者间不相容的格式的差异性与动词*link*过量使用於指 称*编译完成後的事情*与*当编译好的程式使用时所发生的事情*这两件事上头, 使得这一章节变得复杂了许多。( and, actually, the overloading of the word `load' in a comparable but opposite sense)不过,再复杂也就是这样 了,所以阁下不必过於担心。 为了稍微减轻读者的困惑,我们称执行期间所发生的事为*动态载入*,这一主题 会在下一章节中谈到。你也会在别的地方看到我把动态载入描述成*动态连结*, 不过不会是在这一章节中。换句话说,这一章节所谈的,全部是指发生在编译结 束後的连结。 6.1 共享程式库 vs静态程式库 建立程式的最後一个步骤便是连结;也就是将所有分散的小程式组合起来,看看 是否遗漏了些什麽。显然,有一些事情是很多程式都会想做的---例如,开启档案 ,接著所有与开档有关的小程式就会将储存程式库的相关档案提供给你的程式使 用。在一般的Linux系统上,这些小程式可以在/lib与/usr/lib/目录底下找到。 当你用的是静态的程式库时,连结器会找出程式所需的模组,然後实际将它们拷 贝到执行档内。然而,对共享程式库而言,就不是这样了。共享程式库会在执行 档内留下一个记号,指明*当程式执行时,首先必须载入这个程式库*。显然,共 享程式库是试图使执行档变得更小,等同於使用更少的记忆体与磁碟空间 。Linux内定的行为是连结共享程式库,只要Linux能找到这些共享程式库的话, 就没什麽问题;不然,Linux就会连结静态的了。如果你想要共享程式库的话,检 查这些程式库(*.sa for a.out, *.so for ELF)是否住在它们该在的地方,而 且是可读取的。 在Linux上,静态程式库会有类似libname.a这样的名称;而共享程式库则称 为libname.so.x.y.z,此处的x.y.z是指版本序号的样式。共享程式库通常都会有 连结符号指向静态程式库(很重要的)与相关联的.sa档案。标准的程式库会包含 共享与静态程式库两种格式。 你可以用ldd(List Dynamic Dependencies)来查出某支程式需要哪些共享程式 库。 $ ldd /usr/bin/lynx libncurses.so.1 => /usr/lib/libncurses.so.1.9.6 libc.so.5 => /lib/libc.so.5.2.18 这是说在我的系统上,WWW浏览器*lynx*会依赖libc.so.5 (the C library) 与libncurses.so.1(终端机萤幕的控制)的存在。若某支程式缺乏独立性, ldd就会说‘statically linked’或是‘statically linked (ELF)’。 6.2 终极审判(‘sin() 在哪个程式库里?’) nm 程式库名称应该会列出此程式库名称所参考到的所有符号。这个指令可以应用 在静态与共享程式库上。假设你想知道tcgetattr()是在哪儿定义的:你可以如此 做, $ nm libncurses.so.1 |grep tcget U tcgetattr *U*指出*未定义*---也就是说ncurses程式库有用到tegetattr(),但是并没有定 义它。你也可以这样做, $ nm libc.so.5 | grep tcget 00010fe8 T __tcgetattr 00010fe8 W tcgetattr 00068718 T tcgetpgrp *W*说明了*弱态(weak)*,意指符号虽已定义,但可由不同程式库中的另一定义所 替代。最简单的*正常*定义(像是tcgetpgrp)是由*T*所标示: 标题所谈的问题,最简明的答案便是libm.(so|a)了。所有定义在的函数 都保留在maths程式库内;因此,当你用到其中任何一个函数时,都需要以-lm的 参数连结此程式库。 6.3 X档案? ld: Output file requires shared library `libfoo.so.1` ld与其相类似的命令在搜寻档案的策略上,会依据版本的差异而有所不同,但是 唯一一个你可以合理假设的内定目录便是/usr/lib了。如果你希望身处它处的程 式库也列入搜寻的行列中,那麽你就必须以-L选项告知gcc或是ld。 要是你发现一点效果也没有,就赶紧察看看那档案是不是还乖乖的躺在原地。 就a.out而言,以-lfoo参数来连结,会驱使ld去寻找libfoo.sa(shared stubs) ;如果没有成功,就会换成寻找libfoo.a(static)。就ELF而言, ld会先 找libfoo.so,然後是libfoo.a。libfoo.so通常是一个连结符号,连结 至libfoo.so.x。 6.4 建立你自己的程式库 控制版本 与其它任何的程式一样,程式库也有修正不完的bugs的问题存在。它们也可能产 生出一些新的特点,更改目前存在的模组的功效,或是将旧的移除掉。这对正在 使用它们的程式而言,可能会是一个大问题。如果有一支程式是根据那些旧的特 点来执行的话,那怎麽办? 所以,我们引进了程式库版本编号的观念。我们将程式库*次要*与*主要*的变更 分门别类,同时规定*次要*的变更是不允许用到这程式库的旧程式发生中断的现 象。你可以从程式库的档名分辨出它的版本(实际上,严格来讲,对ELF而言仅仅 是一场天大的谎言;继续读将下去,便可明白为什麽了): libfoo.so.1.2的主 要版本是1,次要版本是2。次要版本的编号可能真有其事,也可能什麽都没 有---libc在这一点上用了*修正程度*的观念,而订出了像libc.so.5.2.18这样的 程式库名称。次要版本的编号内若是放一些字母、底线、或是任何可以列印 的ASCII字元,也是很合理的。 ELF与a.out格式最主要的差别之一就是在设置共享程式库这件事上;我们先 看ELF,因为它比较简单一些。 ELF?它到底是什麽东东ㄋㄟ? ELF(Executable and Linking Format)最初是由USL(UNIX System Laboratories)发展而成的二进位格式,目前正应用於Solaris与System V Release 4上。由於ELF所增涨的弹性远远超过Linux过去所用的a.out格式,因 此GCC与C程式库的发展人士於1995年决定改用ELF为Linux标准的二进位格式。 怎麽又来了? 这一节是来自於‘/news-archives/comp.sys.sun.misc’的文件。 ELF(“Executable Linking Format”)是於SVR4所引进的新式改良目的档格 式。ELF比起COFF可是多出了不少的功能。以ELF而言,它*是*可由使用者自行 延伸的。ELF视一目的档为节区(sections),如串列般的组合;而且此串列 可为任意的长度(而不是一固定大小的阵列)。这些节区与COFF的不一样,并 不需要固定在某个地方,也不需要以某种顺序排列。如果使用者希望补捉到新 的资料,便可以加入新的节区到目的档内。ELF也有一个更强而有力的除错法 式,称为DWARF(Debugging With Attribute Record Format)□目前Linux并 不完全支援。DWARF DIEs(Debugging Information Entries)的连结串列会 在ELF内形成 .debug的节区。DWARF DIEs的每一个 .debug节区并非一些少量 且固定大小的资讯记录的集合,而是一任意长度的串列,拥有复杂的属性,而 且程式的资料会以有□围限制的树状资料结构写出来。DIEs所能补捉到的大量 资讯是COFF的 .debug节区无法望其项背的。(像是C++的继承图。) ELF档案是从SVR4(Solaris 2.0 ?)ELF存取程式库(ELF access library) 内存取的。此程式库可提供一简便快速的介面予ELF。使用ELF存取程式库最主 要的恩惠之一便是,你不再需要去察看一个ELF档的qua了。就UNIX的档案而言 ,它是以Elf*的型式来存取;呼叫elf_open()之後,从此时开始,你只需呼 叫elf_foobar()来处理档案的某一部份即可,并不需要把档案实际在磁碟上 的image搞得一团乱。 ELF的优缺点与升级至ELF等级所需经历的种种痛苦,已在ELF-HOWTO内论及;我并 不打算在这儿涂浆糊。ELF HOWTO应该与这份文件有同样的主题才是。 ELF共享程式库 若想让libfoo.so成为共享程式库,基本的步骤会像下面这样: $ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o $ ln -s libfoo.so.1.0 libfoo.so.1 $ ln -s libfoo.so.1 libfoo.so $ LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH 这会产生一个名为libfoo.so.1.0的共享程式库,以及给予ld适当的连结 (libfoo.so)还有使得动态载入程式(dynamic loader)能找到它 (libfoo.so.1)。为了进行测试,我们将目前的目录加到LD_LIBRARY_PATH里。 当你津津乐道於程式库制做成功之时,别忘了把它移到如/usr/local/lib的目录 底下,并且重新设定正确的连结路径。libfoo.so.1与libfoo.so.1.0的连结会 由ldconfig依日期不断的更新,就大部份的系统来说,ldconfig会在开机过程中 执行。libfoo.so的连结必须由手动方式更新。如果你对程式库所有组成份子(如 标头档等)的升级,总是抱持著一丝不□的态度,那麽最简单的方法就是 让libfoo.so -> libfoo.so.1;如此一来,ldconfig便会替你同时保留最新的连 结。要是你没有这麽做,你自行设定的东东就会在数日後造成千奇百怪的问题出 现。到时候,可别说我没提醒你啊! $ su # cp libfoo.so.1.0 /usr/local/lib # /sbin/ldconfig # ( cd /usr/local/lib ; ln -s libfoo.so.1 libfoo.so ) 版本编号、soname与符号连结 每一个程式库都有一个soname。当连结器发现它正在搜寻的程式库中有这样的一 个名称,连结器便会将soname箝入连结中的二进位档内,而不是它正在运作的实 际的档名。在程式执行期间,动态载入程式会搜寻拥有soname这样的档名的档案 ,而不是程式库的档名。因此,一个名为libfoo.so的程式库,就可以有一 个libbar.so的soname了。而且所有连结到libbar.so的程式,当程式开始执行时 ,会寻找的便是libbar.so了。 这听起来好像一点意义也没有,但是这一点,对於了解数个不同版本的同一个程 式库是如何在单一系统上共存的原因,却是关键之钥。Linux程式库标准的命名方 式,比如说是libfoo.so.1.2,而且给这个程式库一个libfoo.so.1的soname。如 果此程式库是加到标准程式库的目录底下(e.g. /usr/lib),ldconfig会建立符 号连结libfoo.so.1 -> libfoo.so.1.2,使其正确的image能於执行期间找到。你 也需要连结libfoo.so -> libfoo.so.1,使ld能於连结期间找到正确的soname。 所以罗,当你修正程式库内的bugs,或是添加了新的函数进去(任何不会对现存 的程式造成不利的影响的改变),你会重建此程式库,保留原本已有的soname, 然後更改程式库档名。当你对程式库的变更会使得现有的程式中断,那麽你只需 增加soname中的编号---此例中,称新版本为libfoo.so.2.0,而soname变 成libfoo.so.2。紧接著,再将libfoo.so的连结转向新的版本;至此,世界又再 度恢复了和平! 其实你不须要以此种方式来替程式库命名,不过这的确是个好的传统。ELF赋予你 在程式库命名上的弹性,会使得人气喘呼呼的搞不清楚状况;有这样的弹性在, 也并不表示你就得去用它。 ELF总结:假设经由你睿智的观察发现有个惯例说:程式库主要的升级会破坏相容 性;而次要的升级则可能不会;那麽以下面的方式来连结,所有的一切就都会相 安无事了。 gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor a.out---旧旧的格式□ 建立共享程式库的便利性是升级至ELF的主要原因之一。那也是说,a.out可能还是 有用处在的。上ftp站去抓 [22]ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz; 解压缩後你会发现有20页的文件可以慢慢的读哩。我很不喜欢自己党派的偏见表 现得那麽的淋璃尽致,可是从上下文间,应该也可以很清楚的嗅出我从来不拿石 头砸自己的脚的脾气吧!:-) ZMAGIC vs QMAGIC QMAGIC是一种类似旧格式的a.out(亦称为ZMAGIC)的可执行档 格式,这种格式 会使得第一个分页无法map。当0-4096的□围内没有mapping存在时,则可允 许NULL dereference trapping更加的容易。所产生的边界效应是你的执行档会比 较小(大约少1K左右)。 只有即将作废的连结器有支援ZMAGIC,一半已埋入棺材的连结器有支援这两种格 式;而目前的版本仅支援QMAGIC而已。事实上,这并没有多大的影响,那是因为 目前的核心两种格式都能执行。 *file*命令应该可以确认程式是不是QMAGIC的格式的。 档案配置 一a.out(DLL)的共享程式库包含两个真实的档案与一个连结符号。就*foo*这个用 於整份文件做为□例的程式库而言,这些档案会是libfoo.sa与libfoo.so.1.2; 连结符号会是libfoo.so.1,而且会指向libfoo.so.1.2。这些是做什麽用的? 在编译时,ld会寻找libfoo.sa。这是程式库的*stub*档案。而且含有所有执行期 间连结所需的exported的资料与指向函数的指标。 执行期间,动态载入程式会寻找libfoo.so.1。这仅仅是一个符号连结,而不是真 实的档案。故程式库可更新成较新的且已修正错误的版本,而不会损毁任何此时 正在使用此程式库的应用程式。在新版---比如说libfoo.so.1.3---已完整呈现时 ,ldconfig会以一极微小的操作,将连结指向新的版本,使得任何原本使用旧版 的程式不会感到丝毫的不悦。 DLL程式库(我知道这是无谓的反覆---所以对我提出诉讼吧!)通常会比它们的 静态副本要来得大多。它们是以*洞(holes)*的形式来保留空间以便日後的扩充 。这种*洞*可以不占用任何的磁碟空间。一个简单的cp呼叫,或是使用makehole 程式,就可以达到这样效果。因为它们的位址是固定在同一位置上,所以在建立 程式库後,你可以把它们拿掉。不过,千万不要试著拿掉ELF的程式库。 ``libc-lite''? libc-lite是轻量级的libc版本。可用来存放在磁碟片上,也可以替大部份低微 的UNIX任务收尾。它没有包含curses, dbm, termcap等等的程式码。如果你 的/lib/libc.so.4是连结到一个lite的libc,那麽建议你以完整的版本取代它。 连结:常见的问题 把你连结时所遭遇的问题寄给我!我可能什麽事也不会做,但是只要累积了足够 的数量,我会把它们写起来*。 你想共享,偏偏程式却连结成静态的! 检查你提供给ld的连结是否正确,使ld能找到每一个对应的共享程式库, 就ELF而言,这是指一个符号连结libfoo.so,连结至image;就a.out而言 ,就是libfoo.sa档了。很多人将ELF binutils 2.5升级至2.6之後,就产 生了这个问题---早期的版本搜寻共享程式库时较有智慧,所以并没有将 所有的连结建立起来。後来,为了与其它的架构相容,这项充满智慧的行 为被人给删除掉了,另外,这样的*智慧*判断错误的机率相当高,所造成 的麻烦比它所解决的问题还多,所以留著也是害人精;不如归去兮! DLL的工具程式‘mkimage’找不到libgcc? 自libc.so.4.5.x之後,libgcc已不再是共享的格式。因此,你必须 在*-lgcc*出现之处以`gcc -print-libgcc-file-name`替代(完整的倒单 引号(back-quotes))。另外,删除所有/usr/lib/libgcc*的档案。这 点很重要哩。 __NEEDS_SHRLIB_libc_4 multiply defined messages 是同样的问题所造成的另一种结果。 ``Assertion failure'' message when rebuilding a DLL ? 这一条神秘的讯息最有可能的原因是,在原始的jump.vars档案内,由於 保留的空间太少,以致於造成其中一个jump table slots溢满。你可以执 行工具程式□由2.17.tar.gz套件所提供的‘getsize’命令,定出所有嫌 疑犯的踪迹。可能唯一的解决方法是,解除此程式库主要的版本编号,强 迫它回到不相容的年代。 ld: output file needs shared library libc.so.4 通常这是发生在当你连结的程式库不是libc(如X程式库),而且在命令 列用了-g的参数,却没有一并使用-static,所发出的错误讯息。 共享程式库的.sa stubs通常有一个未定义的符号_NEEDS_SHRLIB_libc_4 ;这一点可藉由libc.sa stub来解决,然而,以-g来编译时,会使得连结 以libg.a或libc.a来结束;因此这个符号一直就没有解决,也就会导致上 面的错误讯息了。 总之,以-g的旗号编译时别忘了加上-static,不然就别用-g来连结。通 常,以-g编译各个独立的档案时,所获得的除错资讯已经足够,连结时就 可以不需要它了。 7. 动态载入 这一章节目前是简短了一点;当我掠尽ELF HOWTO时,就是这部份再度扩展的时候 了。 7.1 基本概念 Linux有共享程式库,如果之前你已坐著读完上一章节,想必现在一听到像这样的 说词,便会立刻感到头昏。有一些照惯例而言是在连结时期便该完成的工作,必 须延迟到载入时期才能完成。 7.2 错误讯息 把你连结的错误寄给我!我不会做任何的事,不过我可以把它们写起来** can't load library: /lib/libxxx.so, Incompatible version (a. out only) 这是指你没有xxx程式库的正确的主要版本。可别以为 随随 便便弄个连结到你目前拥有的版本就可以了,如果幸运的话,就只 会造成你的程式分页错误而已。去抓新的版本.ELF类似的情况会造成像下 面这样的讯息: ftp: can't load library 'libreadline.so.2' warning using incompatible library version xxx (a. out only)你的程式库的次要版本比起这支程式用来编译的还要旧 。程式依然可以执行。只是可能啦!我想,升个级应该没什麽伤害吧! 7.3 控制动态载入器的运作 有一组环境变数会让动态载入器有所反应。大部份的环境变数对ldd的用途要比起 对一般users的还要来得更多。而且可以很方便的设定成由ldd配合各种参数来执 行。这些变数包括, * LD_BIND_NOW --- 正常来讲,函数在呼叫之前是不会让程式寻找的。设定这 个旗号会使得程式库一载入,所有的寻找便会发生,同时也造成起始的时间 较慢。当你想测试程式,确定所有的连结都没有问题时,这项旗号就变得很 有用。 * LD_PRELOAD可以设定一个档案,使其具有*覆盖*函数定义的能力。例如,如 果你要测试记忆体分配的方略,而且还想置换*malloc*,那麽你可以写好准 备替换的副程式,并把它编译成mallolc.,然後: $ LD_PRELOAD=malloc.o; export LD_PRELOAD $ some_test_program LD_ELF_PRELOAD 与 LD_AOUT_PRELOAD 很类似,但是仅适用於正确的二进位 型态。如果设定了 LD_something_PRELOAD 与 LD_PRELOAD ,比较明确的那 一个会被用到。 * LD_LIBRARY_PATH是一连串以分号隔离的目录名称,用来搜寻共享程式库。 对ld而言,并没有任何的影响;这项只有在执行期间才有影响。另外,对执 行setuid与setgid的程式而言,这一项是无效的。而LD_ELF_LIBRARY_PATH 与LD_AOUT_LIBRARY_PATH这两种旗号可根据各别的二进位型式分别导向不同 的搜寻路径。一般正常的运作下,不应该会用到LD_LIBRARY_PATH;把需要搜 寻的目录加到/etc/ld.so.conf/里;然後重新执行ldconfig。 * LD_NOWARN 仅适用於a.out。一旦设定了这一项(LD_NOWARN=true; export LD_NOWARN),它会告诉载入器必须处理fatal-warnings(像是次要版本不相 容等)的警告讯息。 * LD_WARN仅适用於ELF。设定这一项时,它会将通常是致命讯息的“Can*t find library”转换成警告讯息。对正常的操作而言,这并没有多大的用处 ,可是对ldd就很重要了。 * LD_TRACE_LOADED_OBJECTS仅适用於ELF。而且会使得程式以为它们是由ldd所 执行的: $ LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynx libncurses.so.1 => /usr/lib/libncurses.so.1.9.6 libc.so.5 => /lib/libc.so.5.2.18 7.4 以动态载入撰写程式 如果你很熟悉Solaris 2.x所支援的动态载入的工作的话,你会发现Linux在这点 上与其非常的相近。这一部份在H.J.Lu的ELF程式设计文件内与dlopen(3) 的manual page(可以在ld.so的套件上找到)上有广泛的讨论。这里有个不错的 简单□例:以-ldl连结。 #include #include main() { void *libc; void (*printf_call)(); if(libc=dlopen("/lib/libc.so.5",RTLD_LAZY)) { printf_call=dlsym(libc,"printf"); (*printf_call)("hello, world\n"); } } 8. 与发展人士联络 8.1 Bug报表 把问题写下来。这是针对Linux的,亦或是gcc在其它系统上所发生的问题。 与kernel的版本相关吗?或者是程式库的版本?如果改用静态方式连结,问题是 不是就消失了?你可以节录一小段程式来展示这只bug吗? 当你做了这些事情之後,你将会知道程式内的bugs是什麽。就gcc而言,bug报表 程序是以info档来说明的。如果是ld.so或是C、maths程式库,将email寄 到linux-gcc@vger.rutgers.edu。如果可能的话,包含一支自己自足的小程式以 展示这个bug,而且附上说明,描述你想要让这支程式做些什麽与实际上它又做了 些什麽。 8.2 协助发展 如果你想要帮忙发展gcc或是C程式库,第一件事就是加 入linux-gcc@vger.rutgers.edu通信论坛。如果你只是想看看通信论坛在讨论些 什麽,这里有一个论坛的archives,位於 [23]http://homer.ncm.com/linux-gcc/。接下来的事,就看你想做什麽了。 9. 结语 9.1 名人榜 Only presidents, editors, and people with tapeworms have the right to use the editorial ``we''. (Mark Twain) 这份HOWTO文件几乎完全根植於Mitchum Dsouza的GCC-FAQ; 文件中大部份的资讯 是直接来自於GCC-FAQ。这份文件用到的第一人称代名词,可视为我们两人其中一 个;可以说,要是其中有一个人说“我还没有测试过这些;如果它烧了你的硬碟 、毁了你的系统或是搞得你妻离子散的,可别说我没提醒你啊!",像这样的话适 用於我俩身上。 对这份文件有贡献的名人雅士如下所列(以ASCII码的顺序列出): Andrew Tefft, Axel Boldt, Bill Metzenthen, Bruce Evans, Bruno Haible, Daniel Barlow, Daniel Quinlan, David Engel, Dirk Hohndel, Eric Youngdale, Fergus Henderson, H.J. Lu, Jens Schweikhardt, Kai Petzke, Michael Meissner, Mitchum DSouza, Olaf Flebbe, Paul Gortmaker, Rik Faith, Steven S. Dick, Tuomas J Lukka, 当然还有Linux Torvalds,没有了他,这整 个运动就变得一点意义也没有了,所以不可能让他孤单的。:-) 请不要觉得有任何的冒犯之处,如果您的名字没有出现在这儿。如果您对这份文 件(HOWTO或是FAQ)曾经有过贡献的话,请email给我,我会改正过来的。 9.2 翻译 到目前为止,这份文件还没有已知的翻译版本出现。如果你希望生一个出来,请 尽管去做,不过一定得告诉我相关的事宜。我讲的语言会是你想要翻的语言,那 机率(很遗憾)只有好几百分之一,不过还是先把悲伤摆一旁吧,不管是什麽样 问题,我都会很乐意帮忙的。 9.3 欢迎任何的回馈 寄信给我 [24]dan@detached.demon.co.uk。我的PGP public key (ID 5F263625) 可在我的烘培鸡 [25]web pages上使用,如果你觉得事情有必要保密 的话。 9.4 合法的行迳规定 本文采用过的商标分属於个别的持有者。 这份HOWTO文件的版权属於Daniel Barlow 。 你可 以任何型式的媒体,包括有形的、电子的方式重复生产、传布部份或是整份 的GCC-HOWTO,只要这份版权声明保存在所有的复制品中。商业的交易是允许的, 而且我鼓励你这麽做,不过,我很希望你能通知我有关的细节。 不论你做什麽,像是翻译、扩充这份文件、从这份文件衍生出其他相关的文件或 是整合其他的Linux HOWTO文件等,都必须遵循这份版权声明。也就是说,如果你 参酌HOWTO文件写出其他的说明文件,你是不可以对这份衍生出来的文件加上额外 的限制,影响文件的发行权的。当然,有些特别的情况是可以例外的,关於这点 ,请与Linux的协调人连络,电子邮件信箱的地址就在後头。 总而言之,我们希望所有的HOWTO文件能够透过各种不同的管道尽可能的传播出去 ,然而,同样也绝对希望这份版权声明可以存留在文件里头,其他任何发 行HOWTOs文件的计画,我们都很愿意接到你的通知。 如果有任何问题,请与Linux HOWTO的协调人Tim Bynum连系,email address 是gregh@sunsite.unc.edu。 原文转载如下: All trademarks used in this document are acknowledged as being owned by their respective owners. This document is copyright (C) 1996 Daniel Barlow It may be reproduced and distributed in whole or in part, in any medium physical or electronic, as long as this copyright notice is retained on all copies. Commercial redistribution is allowed and encouraged; however, the author would like to be notified of any such distributions. All translations, derivative works, or aggregate works incorporating any Linux HOWTO documents must be covered under this copyright notice. That is, you may not produce a derivative work from a HOWTO and impose additional restrictions on its distribution. Exceptions to these rules may be granted under certain conditions; please contact the Linux HOWTO coordinator at the address given below. In short, we wish to promote dissemination of this information through as many channels as possible. However, we do wish to retain copyright on the HOWTO documents, and would like to be notified of any plans to redistribute the HOWTOs. If you have questions, please contact Tim Bynum, the Linux HOWTO coordinator, at linux-howto@sunsite.unc.edu via email. 6/3/98增修 10. 索引 开头如果不是文数字的字元,排列就按照ASCII码的顺序。 * -fwritable-strings [26]39 [27]56 * /lib/cpp [28]16 * a.out [29]1 * ar [30]10 * as [31]8 * [32]19 * atoi() [33]40 * atol() [34]41 * binaries too big [35]63 [36]65 [37]77 * chewing gum [38]3 * cos() [39]68 * debugging [40]59 * dlopen() [41]82 * dlsym() [42]83 * documentation [43]4 * EINTR [44]52 * elf [45]0 [46]71 * execl() [47]57 * fcntl [48]47 * FD_CLR [49]44 * FD_ISSET [50]45 * FD_SET [51]43 * FD_ZERO [52]46 * file [53]2 * [54]20 * gcc [55]6 * gcc -fomit-frame-pointer [56]61 * gcc -g [57]60 * gcc -v [58]14 * gcc, bugs [59]15 [60]28 [61]29 [62]84 * gcc, flags [63]13 [64]25 [65]26 * gdb [66]64 * header files [67]17 * interrupted system calls [68]51 * ld [69]9 * LD_* environment variables [70]80 * ldd [71]81 * libc [72]7 * libg.a [73]62 * libgcc [74]79 * [75]21 * lint [76]58 * [77]18 * manual pages [78]5 * [79]70 * maths [80]69 * mktemp() [81]55 * optimisation [82]27 * QMAGIC [83]76 * segmentation fault [84]30 [85]54 * segmentation fault, in GCC [86]33 * select() [87]50 * SIGBUS [88]34 * SIGEMT [89]35 * SIGIOT [90]36 * SIGSEGV [91]31 [92]53 * SIGSEGV, in gcc [93]32 * SIGSYS [94]38 * SIGTRAP [95]37 * sin() [96]67 * soname [97]73 * sprintf() [98]42 * statically linked binaries, unexpected [99]66 [100]78 * [101]23 * [102]24 * strings [103]11 * [104]48 * [105]49 * [106]22 * version numbers [107]12 [108]74 * weird things [109]72 * ZMAGIC [110]75 References 1. http://www.linux.org.tw/CLDP/ 2. http://linux.ntcic.edu.tw/~jsfrank/ 3. http://sunsite.unc.edu/pub/linux/docs/HOWTO/ 4. http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html 5. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ 6. ftp://sunsite.unc.edu/pub/Linux/docs/ 7. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ 8. ftp://prep.ai.mit.edu/pub/gnu/ 9. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ 10. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ 11. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ 12. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ 13. ftp://sunsite.unc.edu/pub/Linux/devel/msdos 14. ftp://prep.ai.mit.edu/pub/gnu 15. ftp://larch.lcs.mit.edu/pub/Larch/lclint 16. ftp://prep.ai.mit.edu/pub/gnu 17. ftp://tsx-11.mit.edu/pub/linux/packages/GCC 18. ftp://ftp.x.org/contrib/xxgdb-1.08.tar.gz 19. ftp://sunsite.unc.edu/pub/Linux/devel/debuggers/ 20. ftp://ftp.x.org/contrib/ups-2.45.2.tar.Z 21. ftp://ftp.std.com/pub/jrs/ 22. ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz 23. http://homer.ncm.com/linux-gcc/ 24. mailto:dan@detached.demon.co.uk 25. http://ftp.linux.org.uk/~barlow/ 26. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.39 27. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.56 28. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.16 29. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.1 30. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.10 31. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.8 32. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.19 33. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.40 34. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.41 35. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.63 36. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.65 37. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.77 38. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.3 39. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.68 40. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.59 41. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.82 42. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.83 43. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.4 44. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.52 45. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.0 46. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.71 47. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.57 48. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.47 49. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.44 50. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.45 51. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.43 52. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.46 53. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.2 54. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.20 55. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.6 56. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.61 57. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.60 58. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.14 59. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.15 60. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.28 61. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.29 62. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.84 63. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.13 64. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.25 65. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.26 66. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.64 67. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.17 68. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.51 69. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.9 70. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.80 71. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.81 72. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.7 73. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.62 74. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.79 75. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.21 76. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.58 77. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.18 78. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.5 79. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.70 80. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.69 81. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.55 82. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.27 83. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.76 84. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.30 85. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.54 86. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.33 87. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.50 88. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.34 89. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.35 90. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.36 91. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.31 92. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.53 93. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.32 94. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.38 95. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.37 96. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.67 97. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.73 98. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.42 99. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.66 100. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.78 101. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.23 102. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.24 103. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.11 104. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.48 105. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.49 106. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.22 107. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.12 108. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.74 109. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.72 110. file://localhost/tmp/zh-sgmltools.9205/GCC-HOWTO.txt.html#index.75