?? 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 tcget
00010fe8 T __tcgetattr
00010fe8 W tcgetattr
00068718 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 + -