📄 gcc-howto-6.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312"> <META NAME="GENERATOR" CONTENT="SGML-Tools 1.0.7"> <TITLE>The Linux GCC HOWTO中译版V0.2: 连结</TITLE> <LINK HREF="GCC-HOWTO-7.html" REL=next> <LINK HREF="GCC-HOWTO-5.html" REL=previous> <LINK HREF="GCC-HOWTO.html#toc6" REL=contents></HEAD><BODY><A HREF="GCC-HOWTO-7.html">Next</A><A HREF="GCC-HOWTO-5.html">Previous</A><A HREF="GCC-HOWTO.html#toc6">Contents</A><HR><H2><A NAME="s6">6. 连结</A></H2><P>由於静态与共享程式库两者间不相容的格式的差异性与动词*link*过量使用於指称*编译完成後的事情*与*当编译好的程式使用时所发生的事情*这两件事上头,使得这一章节变得复杂了许多。( and, actually, the overloading of the word `load' in a comparable but opposite sense)不过,再复杂也就是这样了,所以阁下不必过於担心。<P><P>为了稍微减轻读者的困惑,我们称执行期间所发生的事为*动态载入*,这一主题会在下一章节中谈到。你也会在别的地方看到我把动态载入描述成*动态连结*,不过不会是在这一章节中。换句话说,这一章节所谈的,全部是指发生在编译结束後的连结。<P><H2><A NAME="ss6.1">6.1 共享程式库 vs静态程式库</A></H2><P>建立程式的最後一个步骤便是连结;也就是将所有分散的小程式组合起来,看看是否遗漏了些什麽。显然,有一些事情是很多程式都会想做的---例如,开启档案,接著所有与开档有关的小程式就会将储存程式库的相关档案提供给你的程式使用。在一般的Linux系统上,这些小程式可以在<CODE>/lib</CODE>与<CODE>/usr/lib/</CODE>目录底下找到。<P><A NAME="index.65"></A> <A NAME="index.66"></A> <P>当你用的是静态的程式库时,连结器会找出程式所需的模组,然後实际将它们拷贝到执行档内。然而,对共享程式库而言,就不是这样了。共享程式库会在执行档内留下一个记号,指明*当程式执行时,首先必须载入这个程式库*。显然,共享程式库是试图使执行档变得更小,等同於使用更少的记忆体与磁碟空间。Linux内定的行为是连结共享程式库,只要Linux能找到这些共享程式库的话,就没什麽问题;不然,Linux就会连结静态的了。如果你想要共享程式库的话,检查这些程式库(<CODE>*.sa</CODE> for a.out, <CODE>*.so</CODE> for ELF)是否住在它们该在的地方,而且是可读取的。<P>在Linux上,静态程式库会有类似<CODE>libname.a</CODE>这样的名称;而共享程式库则称为<CODE>libname.so.x.y.z</CODE>,此处的<CODE>x.y.z</CODE>是指版本序号的样式。共享程式库通常都会有连结符号指向静态程式库(很重要的)与相关联的<CODE>.sa</CODE>档案。标准的程式库会包含共享与静态程式库两种格式。<P>你可以用<CODE>ldd</CODE>(List Dynamic Dependencies)来查出某支程式需要哪些共享程式库。<BLOCKQUOTE><CODE><PRE>$ ldd /usr/bin/lynx libncurses.so.1 => /usr/lib/libncurses.so.1.9.6 libc.so.5 => /lib/libc.so.5.2.18</PRE></CODE></BLOCKQUOTE><P>这是说在我的系统上,WWW浏览器*lynx*会依赖<CODE>libc.so.5</CODE> (the C library)与<CODE>libncurses.so.1</CODE>(终端机萤幕的控制)的存在。若某支程式缺乏独立性, <CODE>ldd</CODE>就会说‘<CODE>statically linked</CODE>’或是‘<CODE>statically linked (ELF)</CODE>’。<P><H2><A NAME="index.69"></A> <A NAME="index.68"></A> <A NAME="index.67"></A> <A NAME="ss6.2">6.2 终极审判(‘<CODE>sin()</CODE> 在哪个程式库里?’)</A> </H2><P><CODE>nm </CODE><EM>程式库名称</EM>应该会列出此<EM>程式库名称</EM>所参考到的所有符号。这个指令可以应用在静态与共享程式库上。假设你想知道<CODE>tcgetattr()</CODE>是在哪儿定义的:你可以如此做,<P><BLOCKQUOTE><CODE><PRE>$ nm libncurses.so.1 |grep tcget U tcgetattr</PRE></CODE></BLOCKQUOTE><P>*<CODE>U</CODE>*指出*未定义*---也就是说ncurses程式库有用到tegetattr(),但是并没有定义它。你也可以这样做,<P><BLOCKQUOTE><CODE><PRE>$ nm libc.so.5 | grep tcget00010fe8 T __tcgetattr00010fe8 W tcgetattr00068718 T tcgetpgrp</PRE></CODE></BLOCKQUOTE><P>*<CODE>W</CODE>*说明了*弱态(weak)*,意指符号虽已定义,但可由不同程式库中的另一定义所替代。最简单的*正常*定义(像是<CODE>tcgetpgrp</CODE>)是由*<CODE>T</CODE>*所标示:<P><A NAME="index.70"></A> <P>标题所谈的问题,最简明的答案便是<CODE>libm.(so|a)</CODE>了。所有定义在<CODE><math.h></CODE>的函数都保留在maths程式库内;因此,当你用到其中任何一个函数时,都需要以<CODE>-lm</CODE>的参数连结此程式库。<P><P><H2><A NAME="ss6.3">6.3 X档案?</A></H2><P><CODE>ld: Output file requires shared library `libfoo.so.1`</CODE><P><P> ld与其相类似的命令在搜寻档案的策略上,会依据版本的差异而有所不同,但是唯一一个你可以合理假设的内定目录便是<CODE>/usr/lib</CODE>了。如果你希望身处它处的程式库也列入搜寻的行列中,那麽你就必须以<CODE>-L</CODE>选项告知gcc或是ld。<P><P>要是你发现一点效果也没有,就赶紧察看看那档案是不是还乖乖的躺在原地。就a.out而言,以<CODE>-lfoo</CODE>参数来连结,会驱使ld去寻找<CODE>libfoo.sa</CODE>(shared stubs);如果没有成功,就会换成寻找<CODE>libfoo.a</CODE>(static)。就ELF而言, ld会先找<CODE>libfoo.so</CODE>,然後是<CODE>libfoo.a</CODE>。<CODE>libfoo.so</CODE>通常是一个连结符号,连结至<CODE>libfoo.so.x</CODE>。<P><P><H2><A NAME="ss6.4">6.4 建立你自己的程式库</A></H2><H3>控制版本</H3><P>与其它任何的程式一样,程式库也有修正不完的bugs的问题存在。它们也可能产生出一些新的特点,更改目前存在的模组的功效,或是将旧的移除掉。这对正在使用它们的程式而言,可能会是一个大问题。如果有一支程式是根据那些旧的特点来执行的话,那怎麽办?<P>所以,我们引进了程式库版本编号的观念。我们将程式库*次要*与*主要*的变更分门别类,同时规定*次要*的变更是不允许用到这程式库的旧程式发生中断的现象。你可以从程式库的档名分辨出它的版本(实际上,严格来讲,对ELF而言仅仅是一场天大的谎言;继续读将下去,便可明白为什麽了): <CODE>libfoo.so.1.2</CODE>的主要版本是1,次要版本是2。次要版本的编号可能真有其事,也可能什麽都没有---libc在这一点上用了*修正程度*的观念,而订出了像<CODE>libc.so.5.2.18</CODE>这样的程式库名称。次要版本的编号内若是放一些字母、底线、或是任何可以列印的ASCII字元,也是很合理的。<P>ELF与a.out格式最主要的差别之一就是在设置共享程式库这件事上;我们先看ELF,因为它比较简单一些。<P><H3><A NAME="index.71"></A> ELF?它到底是什麽东东ㄋㄟ? </H3><P> ELF(Executable and Linking Format)最初是由USL(UNIX System Laboratories)发展而成的二进位格式,目前正应用於Solaris与System V Release 4上。由於ELF所增涨的弹性远远超过Linux过去所用的a.out格式,因此GCC与C程式库的发展人士於1995年决定改用ELF为Linux标准的二进位格式。<P><H3>怎麽又来了?</H3><P> 这一节是来自於‘/news-archives/comp.sys.sun.misc’的文件。<P><BLOCKQUOTE>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++的继承图。)</BLOCKQUOTE><P><BLOCKQUOTE>ELF档案是从SVR4(Solaris 2.0 ?)ELF存取程式库(ELF access library)内存取的。此程式库可提供一简便快速的介面予ELF。使用ELF存取程式库最主要的恩惠之一便是,你不再需要去察看一个ELF档的qua了。就UNIX的档案而言,它是以Elf*的型式来存取;呼叫elf_open()之後,从此时开始,你只需呼叫elf_foobar()来处理档案的某一部份即可,并不需要把档案实际在磁碟上的image搞得一团乱。</BLOCKQUOTE>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -