Gcc HowTo(轉)
Gcc HowTo(轉)[@more@]1. 生火上路(Preliminaries)!1.1. ELF vs. a.out目前,Linux的發展正波濤洶湧的進行著.簡單一點講,Linux有兩種執行檔的格式(formats)可用,取決於你的系統是怎麼整合起來的;你可能兩種都有.讀了這份檔案之後,你就會知道是那一種了.那,要怎麼區別呢?執行公用程式(utility)'file' (例如,file /bin/bash)就對了.就ELF格式的程式碼來講,顯示出來的訊息會含有ELF的字眼;如果是a.out格式的,訊息內就會箝有 Linux/i386的字樣了.ELF與a.out格式的差異之處,會在後續的章節中討論(很廣泛喔).ELF是比較新的格式,一般而言,接受的程度較佳.1.2. 作者的私語(Administrata)版權說明(copyright information)與合法的行逕規定(legalese),就擺在這份檔案的尾端.除此之外,我......,我還有一些不得不提醒你的話要講:就算你□著沒事幹,也不要在Usenet上丟一些呆瓜問的問題;還有啊,不要老以為自己C的功力深厚,專門發表一些不是bugs的bugs出來丟人現眼, 告訴別人你不學無術.最後;嚼口香糖的時候,不妨挖挖你的鼻孔(,and picking your nose while chewing gum)! [譯者注:不知道這是那一國的幽默? eh? :-)另一種可能是原文有缺漏字彙, 像是"and not picking your nose while chewing gum."]1.3. 印刷與排版(typography)如果你現在讀的是Postscript,dvi或者是html格式的話,那麼你所看到的字型變化就會比只讀純文字格式的人多一些.特別的是,檔案名稱(filenames),命令(commands),命令的輸出(command output)與摘錄出來的原始碼(source code)等,統統都是打字機的字型樣式(form).這樣做的話,對於某些需要強調的變數(variables)以及沒有特定結果的□例(random things)而言,就可以達到強調的效果了.讀這份檔案的同時,你也會得到一個有用的(usable)索引(index).假若是dvi, postscript之類的版本,索引的數字就是章節(section)的編號;如果是HTML的話,這些數字會按順序排列,你可以用滑鼠左鍵來連結(linking)相對的索引;如果你看的是純(plain)文字版本的話, 數字就只是數字, 沒別的含意;建議你趕快升級為妙哩!我所用的shell是Bourne shell(不是C shell),舉的例子自然是Bourne shell的語法.如果你用的是C shell的話, 環境變數設定的語法會像下面這樣:% setenv FOO bar要是用Bourne shell的話, 我會這樣子寫:$ FOO=bar; export FOO如果提示符號(prompt)顯示的是井字元號#,而不是錢字元號 $,那麼,很有可能是這個命令只適用root而已.當然啦!要是你試了這些□例,結果弄得你的系統發生災變,我可是一點責任也不會負的喔!祝你心情好啊!:-) [譯者注:牽拖(閩南語) _ .]11/8/97譯.2. 上哪抓這些東東?2.1. 這份檔案座落之處這份檔案是Linux HOWTO系列之一.易言之,你可以在所有存放Linux HOWTO檔案的網站上面找到它的芳蹤,例如格式的版本(可能會是較新的版本)可以從~barlow/howto/gcc-howto.html上面抓下來.2.2. 其它相關的說明檔案gcc正式的說明檔案是附在發行的原始碼(source distribution)內(往下看就有了!),裡頭有textinfo與.info兩種檔案.要是你的網路連線速率夠快,或者是有一片cdrom;不然的話,有高度的耐心也成,你可以自己把它untar,然後再把相對應的位元一一複製到/usr/info的目錄底下.假如你的條件與上述的不符,不妨到 tsx-11站上去找一找.不過,我想,沒有必要老是惦記著最新的版本吧.libc的檔案說明有兩種來源.一種是GNU libc,以.info的格式儲存,除了stdio之外,其餘Linux libc的說明都相當詳盡精確.另一種可以在Linux的archivemanpages 上找到系統呼叫(system call)(第2節)與libc函式(function)(第3節)的檔案說明.2.3. GCC解答有二:(a)你可以在ftp://tsx-11.mit.edu:/pub/linux/packages/GCC/的網站上找到正式的Linux GCC發行系統(distribution),且已編譯好的(read-compiled)可執行檔(in binary).當我在寫這份檔案時,2.7.2(gcc-2.7.2.bin.tar.gz)是最新的版本.(b)自由軟體基金會(Free Software Foundation)所釋出的GCC最新原始碼可以從網站GNU archives上取得.沒有必要非得與上述的版本一致才行,不過這個版本的確是目前最新的.Linux GCC的維護人士(maintainers)讓你可以很輕鬆的自行編譯這個最新的版本.configure命令稿(script)會幫你自動建好(set it all up)所有該做的事.建議你有空不妨到tsx-11看看,說不定會有修正的版本(patches)是你會想要用的(apply).如果想要編譯出一些有用的東東(non-trivial)(不是我羅唆,還是有不少細瑣的東東在哩!),下面一小節所談的也是你要具備的:2.4. C程式庫與標頭檔在這兒你該選的是取決於(i)你的系統是ELF亦或是a.out的;(ii)你希望你的系統變成哪一種?如果你是從libc 4升級到libc 5,那麼給你一個良心的建議,去看看ELF-HOWTO檔案.你一定會問,在ELF檔案的哪兒呢?嘿!嘿!不偏不倚,就差不多跟這份檔案一樣的位置.你可以在網站tsx-11上面找到你想要的.libc-5.2.18.bin.tar.gz--- ELF共享程式庫(ELF shared library images),靜態程式庫(static libraries)與標頭檔(include files)(針對C語言與數學程式庫的).libc-5.2.18.tar.gz---libc-5.2.18.bin.tar.gz的原始碼.這兩個檔案你都需要,.bin.套件(package)內含有標頭檔(header files).如果此時你正猶豫不決,不曉得是要老身親自下海,動手編譯C程式庫;還是直接用編譯好的二進位檔(binaries)就可以了.有這種困擾的人,來,看我的嘴形:用人家編譯好的二進位檔不就解決了嘛.只有在你想要NYS或是shadow password的情況下,你才需要自己的手來推動搖籃.libc-4.7.5.bin.tar.gz--- 這個檔案的內容是a.out的共享程式庫(shared library images)與靜態程式庫.這個檔案的用途是為了與前述的libc 5套件共存共榮(coexist)而設計的,不過除非你想要繼續使用或者發展a.out格式的程式,不然的話,是不需要它的.2.5. 相關聯的工具 (as, ld, ar, strings etc)到目前為止,與之前所談的都一樣,從網站tsx-11上,就可以找到這些工具程式.目前的版本是binutils-2.6.0.2.bin.tar.gz.需注意的是binutils只適用於ELF格式,目前libc的版本也都是ELF的;當然啦, 習慣a.out的人如果有個ELF的libc與a.out的libc聯合(in conjunction with)起來一起使用, 那對他們來講是再好不過的美事了.不可否認的,C程式庫的發展正以堅決的(emphatically)腳步邁向ELF格式,除非你真的有很好的理由,需要 a.out的東東(things),不然啊,大家都會鼓勵(encourage)你勇於突破,趁早加入銳不可擋的大潮流裡.11/9/97譯3. GCC的安裝(installation)與啟用(setup)3.1. GCC的版本你可以在shell的提示符號下鍵入gcc -v,螢幕上就會顯示出你目前正在使用的GCC的版本.而這也是一個相當可靠的方法,可以確定你現在所用的是ELF或是a.out.在我的系統上,執行gcc -v的結果是:$ gcc -vReading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specsc c version 2.7.2上面的訊息說明了幾件重要的事情:i486. 這是指出(indicates)你目前在用的gcc是為了486的微處理器(processor)而寫的-可能你的電腦是386或者是586.這3種微處理器的晶片(chips)所編譯而成的程式碼,彼此間是可以相容使用的.差別之處是486的程式碼在某些地方有加上padding的功能,所以可以在 486上面跑得比較快.這對386的機器而言,在執行程式的效能(performance)上並沒有什麼不良的影響detrimental effect),只不過真的(does)會讓程式碼變得稍稍的大了些.box. 這可以說一點也不重要;不過也可能另有所指(像是slackware或者是debian),或者根本什麼也不是(所以羅!完整的目錄名稱是i486-linux).假如你是實踐派的代表,親自動手建立屬於自己的gcc,那麼你可以在建立的過程中(build time)設定這一項,以裝點門面(cosmetic effect).就像我做的一樣:-).linux. 其實這是指linuxelf,或者是linuxaout.這一點會令人引起不必要的困惑,究竟是指哪一種會根據你所用的版本而異.linux 意指ELF若版本序號是2.7.0或是更新的版本;否則的話,就是a.out的了.linuxaout 意指a.out的格式.當linux的定義(definition)從a.out更換到ELF時,linuxaout就會順水推舟,搖身一變,成了一個目標物件(target).因此,你不會看到任何版本新於2.7.0的gcc有linuxaout格式的.linuxelf 已經過時了.通常那是指2.6.3版的gcc,而這個版本也可用來產生ELF的可執行檔(executables).要注意的是,gcc 2.6.3版在產生ELF程式碼時會有bugs-如果你目前用的是這個版本,建議你趕快升級.2.7.2 版本的序號.所以,總結起來,我有2.7.2版的gcc,可以產生ELF格式的程式碼.就這麼簡單,驚訝吧!eh?3.2. 東東裝好後都到哪兒去了?如果安裝gcc時沒有仔細的看著螢幕,或者你是從一個完整的發行系統內把gcc單獨抓出來安裝的話,那麼也許你會想知道到底這些東東裝好後是住在整個檔案系統(file-system)的那個地方.幾個重點如下:/usr/lib/gcc-lib/target/version/ (與子目錄(sub-directories))大部份的編譯器(compilers)就是住在這兒的.在這兒有可執行的程式,實際在做編譯的工作;另外,還有一些特定版本的(version-specific)程式庫與標頭檔include files)等./usr/bin/gcc 指編譯器的驅動程式(driver)--就是你實際在命令列(command line)上執行的程式.這個目錄可供各種版本的gcc使用,只要你用不同的編譯器目錄(如上所述)來安裝就可以了.要知道內定的版本是那一個,在shell提示符號下打gcc -v.要是想強迫執行某個版本,就換打gcc -V version.例如:# gcc -vReading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specsgcc version 2.7.2# gcc -V 2.6.3 -vReading specs from /usr/lib/gcc-lib/i486-box-linux/2.6.3/specsgcc driver version 2.7.2 executing gcc version 2.6.3/usr/target/(bin|lib|include)/. 如果你裝了數種的目標物件(multiple targets),例如a.out與elf,或者某一種的交叉編譯器(cross-compiler)等等;那些屬於非主流目標物件(non-native target(s))的程式庫,binutils(as, ld等等)工具與標頭檔(header files)等都可以在這兒找到.即使你只安裝了一種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目錄下的標頭檔(header files)排除在外的話,Linux還有另外3種主要的標頭檔(header files):/usr/include/與其子目錄下的標頭檔,大部份都是由H.J.Lu發展的libc套件(libc binary package)內所提供的.我會只說'大部份(most)'的原因, 是因為你可能有其它來源的標頭檔(header files)(像是curses與dbm程式庫等等)擺在這兒;尤其是,如果你現在用的是最新的libc進位形式(machine code)儲存之套件,並非原始碼(text),若要以中文全稱譯出,則成'libc二進位檔套件',似有聱牙之嫌,故略去binary,以libc套件通稱.]在核心原始碼的發行系統內(kernel source distribution) ,/usr/include/linux 與 /usr/include/asm (裡頭有這些檔案:and )應該有符號連結(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[回答接下來的問題.通常回答得正不正確並不重要,除非你打算繼續□起(go on and build)你的核心.]# 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/與其它有相類似(相同)目錄名稱的地方(places of that ilk)找到.11/11/97譯3.4. 建立交叉編譯器(Building cross compilers)3.4.1. 將Linux當作目標作業平臺(target platform)假設你已經拿到gcc的原始碼,通常你只要依循INSTALL檔內的指示便可一切ok. make後面黏個configure --target=i486-linux --host=XXX on platform XXX,就能幫你變把戲了(do the trick).要注意的是,你會需要Linux與核心的標頭檔的;而且你也需要建立交叉組譯器(cross assembler)與交叉連結器(cross linker),來源是ftp://tsx-11.mit.edu/pub/linux/packages/GCC/3.4.2. Linux當成來源作業平臺(source platform),MSDOS作為目標作業平臺Ugh.很明顯的,這個大概需要用到"emx"套件(package)或者是"go"延伸套件 (extender).請自行去ftp://sunsite.unc.edu/pub/Linux/devel/msdos看看.我並沒有測試過這個,因此也無法保證(vouch)它的功能(abilities).4. 移植(Porting)與編譯(Compiling)程式4.1. gcc自行定義的符號只要執行gcc時,附加 -v這個引數(switch),就能找出你所用的這版gcc,自動幫你定義了什麼符號(symbols).例如,我的機器看起來會像這樣:$ echo 'main(){printf("hello world");}' | gcc -E -v -Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specsgcc 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獨有的特性(Linux-specific features),那麼把那些無法移植的程式碼(non-portable bits),以條件式編譯(conditional compilation)的前置命令封括(enclose in)起來,可是個不錯的主意呢!#ifdef __linux__/* ... funky stuff ... */#endif /* linux */用__linux__即可達成目的;看仔細一點,不是linux啊.僅管後者也有定義,畢竟,仍然不是POSIX的標準(not POSIX compliant).4.2. 線上求助說明(invocation)gcc編譯器引數(switches)的說明檔案是gcc info page(在Emacs內,按下C-h i,然後選'gcc'的選項).要是弄不出來,不是賣你CD-ROM的人,沒把這個東東壓給你,不然就是你現在用的是舊版的.這種情況下,最好的方法是移動尊臀到archiveftp://prep.ai.mit.edu/pub/gnu或是它的mirrors站臺上,把gcc的原始檔案抓回家,重新烹飪一番.gcc manual page (gcc.1) 可以說是已經過時了.一旦你吃飽撐著沒事幹要去看看它的話,它就會告訴你這件事,叫你別無聊了.4.2.1. 旗正飄飄~(flags)在命令列(command line)上執行gcc時,只要在它的屁股後面加上-On的選項,就能讓gcc乖乖的替你生出最佳化後的機器碼(output code).這裡的n是一個可有可無的小整數.不同的gcc版本,n的意義與其正確的(exact)功效都不一樣;不過,典型的□圍是從0(不要雞婆,我不要最佳化)變化到2(最佳化要多一點),再到3(最佳化要再多一點,多一點).gcc在其內部會將這些轉譯成一系列的-f 與-m選項(options).執行gcc時帶上旗號(flags)-v與-Q,你就能很清楚的看出每一種等級的-O是對應(maps)到那些選項(options).例如,就-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要是你用的最佳化等級(optimization level)高於你的編譯器所能支援的(e.g. -O6),那麼它的效果,就跟你用你的編譯器所能提供的最高等級的,是一樣的結果.說實在的,發行出去的gcc程式碼,用在編譯時竟是如此處理這等問題, 實非什麼好的構想.日後若是有更進步的最佳化方法具體整合到新的版本里,而你(或你的users)還是試著這樣做的話,可能就會發現,gcc會中斷你的程式(break your code)了.從gcc 2.7.0到2.7.2的users應該注意到,使用時-O2會有一個bug存在.更糟糕的是,強度折減(strength reduction)居然沒有用(doesn't work)!要是你喜歡重新編譯gcc的話,是有那麼一個修正的版本(patch)可以更正這項錯誤;不然的話,一定要確定每次編譯時都會加上-fno-strength-reduce喔!11/12/97譯4.2.1.1. 有個性的微處理器(Processor-specific)有一些-m的旗號無法藉由各種等級的-O來開啟,然而卻是十分有用的.這之中最主要的是-m386與-m486兩種,用來告訴gcc該把正在編譯的程式碼視作專為386或是486機器所寫的.不論是用哪一種來編譯程式碼,都可以在彼此的機器上執行,-m486編譯出來的碼會比較大,可是拿來在386的機器上跑也不會比較慢就是了.目前尚無-mpentium或是-m586的旗號.Linus建議我們,可以用-m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2,來得到最佳化的486程式碼(486 code optimizations),而這樣做正好就可以避免alignment(Pentium並不需要)有過大的gaps發生.Michael Meissner說:我的第六感(hunch)告訴我, -mno-strength-reduce(嘿!我可不是在談強度折減的bug啊,那已經是另外一個爭論的戰場了.)一樣也可以在x86的機器上,產生較快的程式碼,這是因為x86的機器對暫存器(register)有著不可磨滅的□渴在(and GCC's method of grouping registers into spill registers vs. other registers doesn't help either).傳統上,強度折減的結果會使得編譯器利用加法暫存器(additional registers)以加法運算(addition)來取代乘法運算(multiplication).而且,我也在懷疑(suspect)-fcaller-saves,可能也只是個漏洞(loss)也說不定. 而我的第七感則再度的告訴我, -fomit-frame-pointer可能會,也可能不會有任何的賺頭.從這點來看,即意謂著有另一個暫存器可用來處理記憶體分配(allocation)的問題.另方面,若純粹從x86的機器在轉換(encodes)它的指令集(instruction set)成為機器碼的方法上來看,便意謂著堆疊(stack)所用到的記憶體空間要比frame所用到的還要來的多;換句話說,Icache對程式碼而言並沒有實質上的益處.若是閣下用了-fomit-frame-pointer的話,同時,也就是告訴編譯器在每次呼叫函式(calls)之後,就必須修正堆疊的指標(stack pointer);然而,就frame來講,若呼叫的次數不多的話,則允許堆疊暫時堆積(accumulate)起來.有關這方面主題的最後一段話仍是來自於Linus:要注意的是,如果你想要得到最佳狀況的執行成果(optimal performance),可千萬別相信我的話.無論如何,一定要進行測試.gcc編譯器還有許多的引數(switches)可用,其中可能就有一種最特別的組合(set),可以給你最佳化的結果喔.11/14/97譯4.2.2. Internal compiler error: cc1 got fatal signal 11 的記憶體.所以,這可能是一個gcc的bug.然而,大部份而言,gcc是一件經過嚴密測試且可靠度佳的軟體佳作.它也用了大量複雜的資料結構與驚人的指標數量.簡言之,若是要評選本世紀最挑惕與最一絲不苟的RAM測試程式(RAM tester)的話,gcc絕對可以一摘后冠的.假如你無法重新複製這隻bug---當你重新開始編譯時,錯誤的訊息並沒有一直出現在同一個地方---那幾乎可以確定,是你的硬體本身有問題(CPU,記憶體,主機板或是快取記憶體).千萬不要因為你的電腦可以透過開機程式的測試(power-on checks),或者Windows可以跑得很順,或者其它什麼的,就回過頭來大肆宣傳說這是gcc的一個bug;你所做的這些測試動作,通常沒有什麼實際上的價值,而且沒有價值也是很合理的結論.另外,也不要因為編譯核心時,總是停留在`make zImage'的階段,就要大罵這是gcc的bug---當然它會停在那兒啊!'做'make zImage'時,需要編譯的檔案可能超過200檔案;我們正在研擬一個比較小的地方來取代.如果你可以重覆產生這個bug,而且(最好是這樣啦)可以寫一個短小的程式來展示這隻bug的話,你就可以把它做成bug報表(bug report),然後email給FSF,或者是linux-gcc郵件表列(linux-gcc mailing list).你可以去參考gcc的說明檔案,看看有什麼詳細的資訊,是他們所需要的.4.3. 移植能力(Portability)據報,近日來許多正面訊息指出,若有某件東東到現在都還沒移植到Linux上去,那麼可以肯定的是,它一定一點價值也沒有.:-)嗯!正經一點.一般而言,原始碼只需要做一些區域性的修改(minor changes),就可以克服(get over)Linux 100%與POSIX相容的特質(compliance).如果你做了任何的修改,而將此部份傳回(passing back)給原作者,會是很有建設性的舉動(worthwhile).這樣日後就只需要用到'make',就能得到一個可執行的檔案了.4.3.1. BSD教派(BSDisms) (有 bsd_ioctl, daemon 與 )編譯程式時,可以配合-I/usr/include/bsd與連結-lbsd的程式庫.(例如:在你的Makefile檔內,把- I/usr/include/bsd加到CFLAGS那一行;把-lbsd加到LDFLAGS那一行).如果你真的那麼想要BSD型態的訊號行為(BSD type signal behavior),也不再需要加上-D__USE_BSD_SIGNAL了.那是因為當你用了-I/usr/include/bsd與含括了標頭檔之後,make就自動會把它加入了.4.3.2. 失落的封印(`Missing' signals)(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訊號,那是因為它們的行為(behavior)與實作方式是息息相關的(implementations dependent),而且也無法進行適當的分門別類(adequately categorized).確認實作方式後(conforming implementations),便可以生產出(deliver)這些訊號,可以必須以檔案說明(document)它們是在什麼樣的環境(circumstances)下生產出來的,以及指出與它們的發展相關的任何限制(any restrictions concerning their delivery)".如欲修正此點,最簡單,也是最笨的(cheesy)方法就是以SIGUNUSED重新定義這些訊號.而正確的方法應是以條件式的編譯#ifdef來處理這些問題才對:#ifdef SIGSYS/* ... non-posix SIGSYS code here .... */#endif1/15/97譯4.3.3. K & Rgcc是個與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會開啟(turn on)-fwritable-strings,使得字串常數(string constants)移至資料記憶體空間(data space)內(從程式碼記憶體空間(text space),這地方是不能任意寫入的).這樣做會讓程式碼的記憶體空間無形中增加的.4.3.4. 前置處理器(Preprocessor)的符號卯上函式原型宣告(prototypes)最常見的問題是,如眾所皆知,Linux中有許多常用的函式都定義成巨集(macros)存放在標頭檔(header files)內,此時若有相似的函式原型宣告出現在程式碼內,前置處理器會拒絕進行語法分析(parse)的前置作業.常見的有atoi()與atol().4.3.5. sprintf()在大部份的Unix系統上, sprintf(string, fmt, ...)傳回的是string的指標,然而,這方面Linux(遵循ANSI)傳回的卻是放入string內的字元數目.進行移植時,尤其是針對SunOS,需有警覺的心.4.3.6. fcntl 與相關的函式; FD_*家族的定義到底擺在哪裡?就在 裡頭. 為了真正的原型宣告,當你用了fcntl,可能你也想含括標頭檔進來.一般而言,函式的manual page會在SYNOPSIS章節內列出需要的標頭檔.4.3.7. select() 的計時(time-out)---程式執行時會處於忙碌-等待的狀態(busy-waiting). 很久很久以前, select()的計時引數(time-out parameter)只有讀的屬性(read-only)而已.即使到了最近,manual pages仍然有下面這段的警告:select()照理講應該是藉由適當的修正時間的數值,再傳回自原始計時(original time-out)開始後所剩餘的時間.未來的版本可能會使這項功能實現.因此,就目前而言,若假定在呼叫select()之後,計時指標(time-out pointer)仍然不會讓人給修正過,可是一種非常不明智的想法喔!未來就在我們的眼前了!至少,在這兒你絕對可以看到. 函式select()傳回的,是扣除等待尚未到達的資料所耗費的時間後,其剩餘的時間值.如果在計時結束時,都沒有資料傳送進來,計時引數(time-out argument)便會設為0;如果接著還有任何的select(),以同樣的time-out structure來呼叫,那麼select()便會立刻結束.若要修正這項問題,只要每次呼叫select()前,都把計時數值(time-out value)放到time-out 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的螢幕右上角,是不是有個圓圓的,會旋轉的地球動畫.那顆球轉得愈快,就表示資料從網路上傳送過來的速率愈慢!4.3.8. 產生中斷的系統呼叫(Interrupted system calls)4.3.8.1. 徵兆(Symptom):當一支程式以Ctrl-Z中止(stop),然後再重新執行(restart)時--或者是其它可以產生Ctrl-C中斷(interruption)訊號的情況,如子程式(child process)終結(termination)等--系統就會抱怨說"interrupted system call"或是"write: unknown error",或者諸如此類的訊息.4.3.8.2. 問題點:POSIX的系統檢查訊號的次數,比起一些舊版的Unix是要多那麼一點.如果是Linux,可能就會執行signal handlers了--非同步地(asynchronously)(計時器的滴答聲) 系統呼叫的傳回值(on return from any system call) 在下列系統呼叫的執行期間: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.就其它的作業系統而言,你需要的可能就是下面這些系統呼叫(system calls)了: creat(), close(), getmsg(), putmsg(), msgrcv(),msgsnd(), recv(), send(), wait(), waitpid(), wait3(), tcdrain(), sigpause(), semop() to this list.在系統呼叫期間,若有一訊號(那支程式本身應準備好handler因應了)產生,handler就會被呼叫.當handler將控制權轉移回系統呼叫時, 它會偵測出它已經產生中斷,而且傳回值會立刻設定成-1,errno設定成EINTR.程式並沒有想到會發生這種事,所以就會bottles out了.有兩種修正的方法可以選擇:(1) 對每個你自行安裝(install)的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_RESTARTsa.sa_flags |= SA_RESTART;#endif#ifdef SA_INTERRUPTsa.sa_flags &= ~ SA_INTERRUPT;#endifsigaction (sig_nr, &sa, (struct sigaction *)0);}要注意的是,當這部份的變更大量應用到系統呼叫之後,呼叫read(), write(),ioctl(), select(), pause() 與 connect()時,你仍然得自行檢查(check for)EINTR.如下所示.(2) 你自己得很明確地(explicitly)檢查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,其內定的行為(default behaviour)是重新執行系統呼叫.若要讓系統呼叫中斷,得使用 SV_INTERRUPT或SA_INTERRUPT旗號.4.3.9. 可以寫入的字串(Writable strings)gcc對其users總懷抱著樂觀的想法(optimistic view),相信當他們打算讓某個字串當作常數來用時---那它就真的只是字串常數而已.因此,這種字串常數會儲存在程式碼的記憶體區段內(in the code area of the program).這塊區域可以page到磁碟機的image上,避免耗掉swap的記憶體空間,而且任何嘗試寫入的舉動都會造成分頁的錯誤(segmentation fault).這可是一種特色呢!對老舊一點的程式而言, 這可能會產生一個問題.例如,呼叫mktemp(),傳遞引數(arguments)是字串常數. mktemp()會嘗試著在*適當的位置(in place)*重新寫入它的引數.修正的方法不外乎(a)以-fwritable-strings編譯,迫使gcc將此常數置放在資料記憶體空間(data space)內.或者(b)將侵犯地權的部份(offending parts)重新改寫,配置一個不為常數的字串(non-constant string),在呼叫前,先以strcpy()將資料複製進去.4.3.10. 為什麼呼叫execl()會失敗?那是因為你呼叫的方式不對.execl的第一個引數是你想要執行的程式名.第二個與接續的引數會變成你所呼叫的程式的argv陣列(array).記住:傳統上,argv[0]是隻有當程式沒有帶著引數執行時,才會有設定值.所以羅,你應該這樣寫:execl("/bin/ls","ls",NULL);而不是隻有,execl("/bin/ls", NULL);執行程式而不帶任何引數(with no arguments),可解釋成(construe)是一種邀請函(invitation),目的是把此程式的動態程式庫獨立(dynamic library dependencies)的特性印出來(print out).至少,a.out是這樣的.就ELF而言,事情就不是這樣了.(如果你想得知此程式庫的資訊,有一些更簡單的介面可用;參考動態載入(dynamic loading)那一章節,或是ldd的manual page.)11/16/97譯5. Debugging and Profiling5.1. Preventative maintenance (lint)lint對Linux而言並沒有很廣泛的用途,主要是因為大部份的人都能滿足於gcc所提供的警告訊息(warnings).可能最有用的就是-Wall引數了---這個引數的用途是要求gcc將所有的警告訊息顯現出來.but probably has more mnemonic value if thought of as the thing you bang your head against.網路上有一個實用的public domain lint,位於ftp://larch.lcs.mit.edu/pub/Larch/lclint.我並不知道這個站到底有多好就是了.5.2. 除錯(Debugging)5.2.1. 我要怎樣做才能將除錯資訊放到一支程式裡頭?你需要新增-g的引數來編譯與連結程式,而且不可以用-fomit-frame-pointer引數.事實上,你不需要重新編譯所有的程式,只需重新編譯目前你正在除錯的部份即可.就a.out的格式(configurations)而言,共享程式庫(shared libraries)是以-fomit-frame-pointer編譯而成,這個時候,gdb就變得英雄無用武之地了.連結時給定-g的選項,應該就隱含著靜態連結(static linking)了;這就是為什麼要加-g的原因了.如果連結器(linker)連結失敗,告訴你找不到libg.a,那就是在/usr/lib/的目錄底下,少了libg.a.libg.a是特殊的C語言偵錯程式庫(special debugging-enabled C library).一般在libc的套件內就會提供libg.a;不然的話(新版是這樣的),你可能需要拿libc的原始碼自己建立了.不過,實際上你應該不需要才對.不管是什麼目的,大部份的情況下,只需將libg.a連結到/usr/lib/libc.a,你就能得到足夠的資訊了.5.2.1.1. 那,能不能把除錯資訊給拿掉?很多的GNU軟體在編譯連結時,都會設定-g的選項,而這樣做會造成執行檔過大的問題(通常是靜態的).實際上,這並不是一個很熱門的想法.如果程式本身有autoconf,產生了configure命令稿,通常你就可以用./configure CFLAGS=或是./configure CFLAGS=-O2來關掉除錯資訊.不然的話,你得檢查檢查Makefile了.當然啦,假如你用的是ELF,程式便會以動態的方式連結(dynamically linked),不論是否有-g的設定;因此你可以平常心把-g拿掉(strip).5.2.2. 實用的軟體(Available software)據瞭解,一般人都使用gdb.你可以從GNU archive sites拿到原始程式;或者是到tsx-11拿可執行檔.xxgdb是一個X介面的除錯程式(debugger),植基於gdb(也就是說你得先安裝好gdb,才能再裝xxgdb).xxgdb的原始碼可以在ftp://ftp.x.org/contrib/xxgdb-1.08.tar.gz找到.另外,UPS除錯程式已由Rick Sladkey移植成功.UPS可以在X底下活得很好,不像xxgdb那樣---僅僅是gdb的X前端介面(X front end).這支除錯程式有一大堆優良的特點,and if you spend any time debugging stuff, you probably should check it out.先前編譯(precompiled)好的Linux版與修正版(patches)的原始碼可以在ftp://sunsite.unc.edu/pub/Linux/devel/debuggers/找到.而最初的原始程式則放在 ftp://ftp.x.org/contrib/ups-2.45.2.tar.Z.你可能會發現另一個用來除錯的工具strace,也是相當的有用.它可以顯示出由程式(process)所產生的系統呼叫,而且還擁有其它眾多繁複的功能(multiplicity),像是如果你手邊沒有原始碼的話,strace可以幫你找出(figure out)有那些路徑(path-names)已編譯進執行檔(binaries)內; exacerbating race conditions in programs that you suspect contain them;還有,strace可拿來學習程式是怎麼在電腦中執行的.最新的版本(目前是3.0.8)可在找到ftp://ftp.std.com/pub/jrs/.5.2.3. 背景程式(Background (daemon) programs)早期典型的常駐程式(daemon programs)是執行fork(),然後終止(terminate)父程式(parent).這樣的做法使得除錯的時間減短了.瞭解(get around)這點的最簡單的方法就是替fork()設一個breakpoint.當程式停止時,強迫fork()傳回0.(gdb) list1 #include23 main()4 {5 if(fork()==0) printf("child");6 else printf("parent");7 }(gdb) break forkBreakpoint 1 at 0x80003b8(gdb) runStarting program: /home/dan/src/hello/./forkBreakpoint 1 at 0x400177c4Breakpoint 1, 0x400177c4 in fork ()(gdb) return 0Make selected stack frame return now? (y or n) y#0 0x80004a8 in main ()at fork.c:55 if(fork()==0) printf("child");(gdb) nextSingle stepping until exit from function fork,which has no line number information.child7 }5.2.4. 核心檔案(Core files)當Linux開機時,通常組態(configuration)會設定成不要產生核心檔案.要是你那麼喜歡它們的話,可以用shell的builtin命令使其重新生效:就C-shell相容的shell(如tcsh)而言,會是下面這樣:% limit core unlimited而類似Bourne shell的shell(sh,bash,zsh,pdksh)則使用下面的語法:$ ulimit -c unlimited如果你想要有個多才多藝(versatility)的核心檔命名(core file naming)(for example, if you're trying to conduct a post-mortem using a debugger that's buggy itself) ,那麼你可以對你的核心程式(kernel)做一點小小的更動(mod).找一找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 0memcpy(corefile+5,current->comm,sizeof(current->comm));#elsecorefile[4] = '';#endif將0換成1.5.3. 旁敲側擊(Profiling)Profiling是用來檢核一支程式中那些部份(which bits)是最常呼叫或是執行的時間最久的方法.這對程式的最佳化與找出何時時間是浪費掉的而言,是相當好的方式.你必須就你所要的時程資訊(timing information)的目的檔案(object files)加上-p來編譯,而且如果要讓輸出的檔案(output files)有意義(make sense),你也會需要gprof(來自binutils套件的命令).參閱gprof的manual page,可得知其細節.11/18/97譯6. 連結(Linking)由於靜態(static)與共享(shared)程式庫兩者間不相容的格式(incompatible binary formats)的差異性(distinction)與動詞*link*過量使用(overloading)於指稱*編譯完成後的事情*與*當編譯過的程式使用時(invoke)所發生的事情*這兩件事上頭,使得這一章節變得複雜了許多.( and, actually, the overloading of the word `load' in a comparable but opposite sense)不過,再複雜也就是這樣了,所以閣下不必過於擔心.為了稍微減輕讀者的困惑,我們稱執行期間(runtime)所發生的事為*動態載入(dynamic loading)*,這一主題會在下一章節中談到.你也會在別的地方看到我把動態載入描述成*動態連結(dynamic linking)*,不過不會是在這一章節中.換句話說,這一章節所談的,全部是指發生在編譯結束後的連結(linking).6.1. 共享程式庫 vs靜態程式庫建立程式的最後一個步驟便是連結;也就是將所有分散的小程式(pieces)組合起來,看看是否遺漏了些什麼.很明顯的,有一些事情是很多程式都會想做的---例如,開啟檔案(open files),接著所有與開檔有關的小程式(pieces)就會以程式庫的檔案型態提供給你的程式.在普通的Linux系統上,這些小程式可以在/lib與/usr/lib/目錄底下找到.當你用的是一靜態的程式庫時,連結器會找出程式所需的模組(bits),然後實際(physically)將它們複製到執行檔內.然而,對共享程式庫而言,就不是這樣了.共享程式庫會在執行檔內留下一個符號(note),指明*當程式執行時,首先必須載入這個程式庫*.很明顯的,共享程式庫是試圖使執行檔變得更小;也等同於使用更少的記憶體與磁碟空間.Linux內定的行為是連結共享程式庫,只要Linux能找到這些共享程式庫的話,就沒什麼問題;不然,Linux就會連結靜態的了.如果你想要共享程式庫的話,檢查這些程式庫(*.sa for a.out, *.so for ELF)是否住在它們該在的地方,而且是可讀取的.在Linux上,靜態程式庫會有類似libname.a這樣的名稱;而共享程式庫則稱做libname.so.x.y.z,此處的x.y.z是指版本序號的樣式.共享程式庫通常都會有連結符號指向靜態程式庫(很重要的)與(on a.out configurations)相關聯的.sa檔案.標準的程式庫會包含共享與靜態程式庫兩種格式.你可以用ldd (List Dynamic Dependencies)來查出某支程式需要哪些共享程式庫.$ ldd /usr/bin/lynxlibncurses.so.1 => /usr/lib/libncurses.so.1.9.6libc.so.5 => /lib/libc.so.5.2.18這是說在我的系統上,WWW瀏覽器(browser)*lynx*會依賴libc.so.5 (the C library)與libncurses.so.1(終端機螢幕的控制)的存在(presence).若某支程式缺乏獨立性(dependencies), ldd就會說`statically linked'或是`statically linked (ELF)'.6.2. Interrogating libraries (`which library is sin() in?')nm 程式庫名稱 應該會列出此程式庫名稱所參考到的所有符號(symbols).這個指令可以應用在靜態與共享程式庫上.假設你想知道tcgetattr()是在哪兒定義的:你可以如此做$ nm libncurses.so.1 |grep tcgetU tcgetattr*U*說明了*未定義(undefined)*---也就是說ncurses程式庫有用到tegetattr(),但是並沒有定義它.你也可以這樣做,$ nm libc.so.5 | grep tcget00010fe8 T __tcgetattr00010fe8 W tcgetattr00068718 T tcgetpgrp*W*說明了*弱勢(weak)*,意指符號雖已定義,但可由不同程式庫中的另一定義所取代(overridden).而最直接的(straightforward)*正常(normal)*定義(像是tcgetpgrp)是由*T*所標示.標題所談的問題,最簡明的答案便是libm.(so|a)了.所有定義在的函式都保留在maths程式庫內;因此,當你用到其中任何一個函式時,都需要以-lm的引數連結此程式庫.6.3. X檔案???ld: Output file requires shared library `libfoo.so.1`ld與其相類似的命令在搜尋檔案的策略(strategy)上,會依據版本的差異而有所不同,但是唯一一個你可以合理假設的內定目錄便是/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. 建立你自己的程式庫(Building your own libraries)6.4.1. 版本控制(Version control)與其它任何的程式一樣,程式庫也有修正不完的bugs的問題存在.它們也可能產生出一些新的特點,更改目前存在的模組的功效,或是將舊的移除掉.這對正在使用它們的程式而言,可能會是一個大問題.如果有一支程式是根據那些舊的特點來執行的話,那怎麼辦?所以,我們引進(introduce)了程式庫版本編號(versioning)的觀念.我們將程式庫*次要(minor)*與*主要(major)*的變更分門別類(categorize),同時我們規定*次要*的變更是不允許用到這程式庫的舊程式發生中斷的現象(break).你可以從程式庫的檔名分辨出它的版本(實際上,嚴格來講,對ELF而言僅僅是一場天大的謊言;繼續讀將下去,便可明白為什麼了): libfoo.so.1.2的主要版本是1,次要版本是2.次要版本的編號可能真有其事,也可能什麼都沒有---libc在這一點上用了*修正程度 (patch-level)*的觀念,而給出了名稱像libc.so.5.2.18這樣的程式庫.次要版本的編號內若是放一些字母,底線,或是任何可以列印的ASCII字元,也是很合理的說.ELF與a.out格式最主要的差別之一就是在建立共享程式庫上.我們先看ELF,因為它比較簡單一些.6.4.2. ELF? 它到底是什麼東東ㄋㄟ?ELF (Executable and Linking Format) 最初是由USL(UNIX System Laboratories)所發展的二進位格式(binary format),而目前正應用於Solaris與System V Release 4上面.由於ELF所增漲的彈性(flexibility)遠遠超過Linux過去所用的a.out格式,因此GCC與C程式庫的發展人士於去年(1995)決定改用ELF為Linux標準的二進位格式.6.4.2.1. 怎麼又來了?這一節是來自於'/news-archives/comp.sys.sun.misc'的檔案.ELF("Executable Linking Format")是於SVR4所引進的新式改良目的檔格式.ELF比起COFF可是多出了不少的功能.以ELF而言,它*是*可由使用者自行延伸的 (user-extensible).ELF視一目的檔為節區(sections)如串列般的組合,而且此串列可為任意的(arbitrarily)長度 (而不是一固定大小的陣列).這些節區與COFF的不一樣,並不需要固定在某個地方,也不需要以某種順序排列.如果users希望能補捉到新的資料,他們便可以加入新的節區到目的檔內.ELF也有一個更強而有力的除錯格式,稱為DWARF(Debugging With Attribute Record Format)-目前Linux並不完全支援.DWARF DIEs(Debugging Information Entries)的連結串列會在ELF內形成.debug的節區.DWARF DIEs的每一個.debug節區並非一些少量的(small)且固定大小的(fixed-size)資訊記錄(information records)的集合(collection),而是一任意長度的串列,擁有複雜的屬性,而且程式的資料會以□圍為根據的樹狀資料結構(scope- based tree)寫出來.DIEs所能補捉到的大量資訊是COFF的.debug節區無法望其項背的.(像是C++的繼承圖).ELF檔案是從SVR4(Solaris 2.0 ?)ELF存取程式庫(ELF access library)記憶體取的.此程式庫可提供一簡便快速的介面予ELF.使用ELF存取程式庫最主要的恩惠之一便是,你不再需要去察看一個ELF檔的qua 了.就UNIX的檔案而言,它是以Elf*的型式來存取;呼叫elf_open()之後,從此時開始,你只需呼叫elf_foobar()來處理檔案的某一部份(components)即可,並不需要把檔案實際在磁碟上的image搞得一團亂.ELF的優缺點與升級至ELF等級所需經歷的種種痛苦(contortions),已在ELF-HOWTO內論及;我並不打算在這兒塗漿糊.ELF HOWTO應該與這份檔案相同之處有同樣的主題才是.6.4.2.2. 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的連結必須由手動方式更新.如果你對程式庫所有組成份子(如標頭檔等)的升級,總是抱持著一絲不□的態度(scrupulous),那麼最簡單的方法就是讓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 )6.4.2.3. 版本編號, soname與符號連結每一個程式庫都有一個soname.當連結器發現它正在搜尋的程式庫中有這樣的一個名稱,連結器便會將soname箝入(embed)連結中的二進位檔內, 而不是它正在運作的實際的檔名.在程式執行期間,動態載入程式會搜尋擁有soname這樣的檔名的檔案,而不是程式庫的檔名.因此,一個名為 libfoo.so的程式庫,就可以有一個libbar.so的soname了.而且所有連結到libbar.so的程式,當程式開始執行時,會尋找的便是libbar.so了.這聽起來好像一點意義也沒有,但是這一點,對於瞭解數個不同版本的同一個程式庫是如何在單一系統上共存(coexist)的原因,卻是關鍵之鑰. Linux程式庫標準的命名方式,比如說是libfoo.so.1.2,而且給這個程式庫一個libfoo.so.1的soname.如果此程式庫是加到標準程式庫的目錄底下(e.g. /usr/lib),ldconfig會建立符號連結libfoo.so.1 -> libfoo.so.1.2,使其正確的image能於執行期間(run-time)找到.你也需要連結libfoo.so -> libfoo.so.1,使ld能於連結期間(link-time)找到正確的soname.所以羅,當你修正程式庫內的bugs,或是新增了新的函式進去(任何不會對現存的程式造成不利的(adversely)影響的改變),你會重建此程式庫, 保留原本已有的soname,然後更改程式庫檔名.而當你對程式庫的變更會使得現有的程式(binaries)中斷(break),那麼你只需增加 soname中的編號---此例中,稱新版本為libfoo.so.2.0,而soname變成libfoo.so.2.緊接著,再將libfoo.so 的連結轉向新的版本;至此,世界又再度恢復了和平!其實你不須要以此種方式來替程式庫命名,不過這的確是個好的傳統(convention).ELF所賦予你在程式庫命名上的彈性,會使得人氣喘呼呼的搞不清楚狀況;有這樣的彈性在,也並不表示你就得去用它.ELF總結:假設經由你睿智的觀察發現有個慣例說:程式庫主要的升級會破壞(break)相容性(compatibility);而次要的升級則可能不會;那麼以下面的方式來連結,所有的一切就都會相安無事了.gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor6.4.3. a.out---舊舊的格式~建立共享程式庫的便利性(ease)是升級至ELF的主要原因之一.那也是說,a.out可能還是有用處在的.上ftp站去抓ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz;解壓縮後你會發現有20頁的檔案可以慢慢的讀哩.我很不喜歡自己黨派的偏見(partisan)表現得那麼的淋璃盡致,可是從上下文間,應該也可以很清楚的嗅出我從來不拿石頭砸自己的腳的脾氣吧!:-)6.4.3.1. ZMAGIC vs QMAGICQMAGIC是一種類似舊格式的a.out(亦稱為ZMAGIC)的可執行檔格式,這種格式會使得第一個分頁無法map.當0-4096的□圍內沒有mapping存在時,則可允許NULL dereference trapping更加的容易.所產生的邊界效應(side effect)是你的執行檔會比較小(大約是1K左右).只有即將作廢的連結器有支援ZMAGIC,一半已埋入棺材的連結器有支援這兩種格式;而目前的版本僅支援QMAGIC而已.事實上,這並沒有多大的影響,那是因為目前的核心兩種格式都能執行.*file*命令應該可以確認程式是不是QMAGIC的格式的.6.4.3.2. 檔案配置(File Placement)一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程式庫(我知道這是無謂的反覆(tautology)---所以對我提出告訴吧!)通常會比它們的靜態副本(static counterparts)要來得大多了.它們是以*洞(holes)*的形式來保留空間以便日後的擴充.這種*洞*可以不佔用任何的磁碟空間.一個簡單的cp呼叫,或是使用makehole程式,就可以達到這樣效果(achieve).因為它們的位址是固定在同一位置上,所以在建立程式庫後,你可以把它們拿掉.千萬不要試著奪走ELF的程式庫.6.4.3.3. ``libc-lite''?libc-lite是輕量級(light-weight)的libc版本.可用來存放在磁碟片上,與替大部份低微的(menial)UNIX任務收尾(suffice).它沒有包含curses, dbm, termcap等等的程式碼.如果你的/lib/libc.so.4是連結到一個lite的libc,那麼建議你以完整的版本取代它.6.4.4. 連結:常見的問題把你連結時所遭遇的問題寄給我!我可能什麼事也不會做,但是隻要累積了足夠的數量, 我會把它們寫起來*.你想共享,偏偏程式卻連結成靜態的!檢查你提供給ld的連結是否正確,使ld能找到每一個對應的共享程式庫.就ELF而言,這是指一個符號連結libfoo.so,連結至image;就 a.out而言,就是libfoo.sa檔了.很多人將ELF binutils 2.5升級至2.6之後,就產生了這個問題---早期的版本搜尋共享程式庫時較有智慧,所以並沒有將所有的連結建立起來.後來,為了與其它的架構相容,這項充滿智慧的行為被人給刪除掉了,另外,這樣的*智慧*判斷錯誤的機率相當高,所造成的麻煩比它所解決的問題還多,所以留著也是害人精,不如歸去兮!The DLL tool `mkimage' fails to find libgcc, or從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 ?這一條神秘的(cryptic)訊息最有可能的原因是,在原始的jump.vars檔案內,由於保留的空間太少, 以致於造成其中一個jump table slots溢滿(overflow).你可以執行tools-2.17.tar.gz套件所提供的`getsize'命令,定出所有嫌疑犯(culprit(s))的蹤跡.可能唯一的解決方法是,解除(bump)此程式庫主要的版本編號,強迫它回到不相容的年代(be backward incompatible).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. 動態載入(Dynamic Loading)這一章節目前是簡短了一點;當我掠盡ELF HOWTO時,就是這部份再度擴充套件的時候了.7.1. 基本概念Linux有共享程式庫,如果之前你已坐著讀完上一章節,想必現在一聽到像這樣的說詞,便會立刻感到頭昏.有一些照慣例而言是在連結時期便該完成的工作(matching-names-to-places),必須延遲到載入時期(load-time)才能完成.7.2. 錯誤訊息(Error messages)把你連結的錯誤寄給我!我不會做任何的事,不過我可以把它們寫起來**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 --- 正常來講,函式在呼叫之前是不會讓程式尋找(looked up)的.設定這個旗號會使得程式庫一載入,所有的尋找(lookups)便會發生,同時也造成起始的時間(startup time)較慢.當你想測試程式,確定所有的連結都沒有問題時,這項旗號就變得很有用.LD_PRELOAD 可以設定一個檔案,使其具有*覆蓋*(overriding)函式定義的能力.例如,如果你要測試記憶體分配的方略(strategies),而且還想置換*malloc*,那麼你可以寫好準備替換的副程式(routine),並把它編譯成mallolc.,然後:$ LD_PRELOAD=malloc.o; export LD_PRELOAD$ some_test_programLD_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; exportLD_NOWARN),它會告訴載入器必須處理fatal-warnings(像是次要版本不相容等)的警告訊息.LD_WARN 僅適用於ELF.設定這一項時,它會將通常是致命訊息的"Can*t find library"轉換成警告訊息.對正常的操作而言,這並沒有多大的用處,可是對ldd就很重要了.LD_TRACE_LOADED_OBJECTS 僅適用於ELF.而且會使得程式以為它們是由ldd所執行的:$ LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynxlibncurses.so.1 => /usr/lib/libncurses.so.1.9.6libc.so.5 => /lib/libc.so.5.2.187.4. 以動態載入撰寫程式如果你很熟悉Solaris 2.x所支援的動態載入的工作的話,你會發現Linux在這點上與其非常的相近.這一部份在H.J.Lu的ELF程式設計檔案內與dlopen(3)的manual page(可以在ld.so的套件上找到)上有廣泛的含蓋(cover).這裡有個不錯的簡單□例:以-ldl連結.#include#includemain(){void *libc;void (*printf_call)();if(libc=dlopen("/lib/libc.so.5",RTLD_LAZY)){printf_call=dlsym(libc,"printf");(*printf_call)("hello, world");}}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郵件表列(mailing list).如果你只是想看看mailing list在討論些什麼,這裡有一個表列的archives,位於接下來的事,就看你想做什麼了.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; 檔案內大部份的資訊(not to mention a reasonable amount of the text)是直接來自於GCC-FAQ的. 這份HOWTO檔案用到的第一人稱代名詞,可視為我們兩人其中一個;通常,要是有人說"我還沒有測試過這些;如果它燒了(toast)你的硬碟/系統/配偶,可別怪我!",那這樣的話適用於我倆身上.對這份檔案有貢獻的名人雅士如下所列(以名字的ASCII碼的順序): AndrewTefft, Axel Boldt, Bill Metzenthen, Bruce Evans, Bruno Haible, DanielBarlow, Daniel Quinlan, David Engel, Dirk Hohndel, Eric Youngdale,Fergus Henderson, H.J. Lu, Jens Schweikhardt, Kai Petzke, MichaelMeissner, Mitchum DSouza, Olaf Flebbe, Paul Gortmaker, Rik Faith,Steven S. Dick, Tuomas J Lukka, 當然還有Linux Torvalds,沒有了他,這整個運動就會變得一點意義也沒有了,所以不可能讓他孤單的.:-)請不要覺得有任何的冒犯之處,如果您的名字沒有出現在這兒,而您對這份檔案(HOWTO或是FAQ)又曾經有過貢獻的話,請email給我,我會改正過來的.9.2. 翻譯到目前為止,這份檔案還沒有已知的翻譯版出現.如果你希望生一個出來,請儘管去做,不過一定得告訴我相關的事宜.我講的語言會是你想要翻的語言,那機率(很遺憾)只有好幾百分之一,不過還是先把這擺一旁吧!不管是什麼方式,我都會很樂意幫忙的.9.3. 歡迎任何的回饋(Feedback)寄信給我dan@detached.demon.co.uk.我的PGP public key (ID 5F263625) 可在我的烘培雞web pages上使用, 如果你覺得事情有必要保密的話.9.4. 合法的行逕規定All trademarks used in this document are acknowledged as beingowned by their respective owners.This document is copyright (C) 1996 Daniel BarlowIt may be reproduced and distributed in whole or in part, in anymedium physical or electronic, as long as this copyright notice isretained on all copies. Commercial redistribution is allowed andencouraged; however, the author would like to be notified of any suchdistributions.All translations, derivative works, or aggregate worksincorporating any Linux HOWTO documents must be covered under thiscopyright notice. That is, you may not produce a derivative work froma 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 informationthrough as many channels as possible. However, we do wish toretain copyright on the HOWTO documents, and would like to benotified of any plans to redistribute the HOWTOs.If you have questions, please contact Greg Hankins, the LinuxHOWTO coordinator, at gregh@sunsite.unc.edu via email.'
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-944670/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Debian APT HOWTO(二)(轉)APT
- APT HOWTO 學習筆記(轉)APT筆記
- gcc 簡介(轉)GC
- GCC 安裝(轉)GC
- [c++/gcc] Centos 7.9升級 gcc 4.8.5 到 gcc11 [轉]C++GCCentOS
- FreeBSD IPsec mini-HOWTO (轉)
- 轉gcc cflags介紹GC
- 用tags/cscope看核心原始碼-HOWTO(轉)原始碼
- GCC 使用指南及gcc最佳編譯引數(轉)GC編譯
- gcc使用手冊(2)(轉)GC
- gcc使用手冊(3)(轉)GC
- gcc使用手冊(4)(轉)GC
- GCC引數詳解(轉)GC
- Linux 2.4 NAT HOWTO 簡體中文版(轉)Linux
- GCC常用命令描述(轉)GC
- gcc最佳編譯引數(轉)GC編譯
- 安裝 GCC 編譯器(轉)GC編譯
- 用gcc批次建mysql庫表(轉)GCMySql
- gcc核心擴充套件linuxforum(轉)GC套件Linux
- linux中gcc的應用(轉)LinuxGC
- 一份非常內行的Linux LVM HOWTO(轉)LinuxLVM
- gcc編譯器小知識FAQ(轉)GC編譯
- gccGC
- Debian APT HOWTO 第三章 軟體包管理(轉)APT
- Oracle HowTo:How to get Oracle SCN?Oracle
- Linux MultiPathing howto.Linux
- 【轉】gcc中的-w -W和-Wall選項GC
- GCC - 一切從這裡開始(轉)GC
- HowTo Restore RMAN Disk backups of RACREST
- GNU 編譯器家族 GCC 內部探密(轉)編譯GC
- 用ccache加速你的gcc/g++編譯(轉)GC編譯
- Win95 + WinNT + Linux multiboot using LILO mini-HOWTO (轉)Linuxboot
- Linux下GCC降低版本 gcc 4.4.6LinuxGC
- gcc編譯GC編譯
- Linux Serial HOWTO 中譯版(zt)Linux
- Linux上安裝GCC編譯器過程(轉)LinuxGC編譯
- 有個GCC的問題,請老大們過目!(轉)GC
- cgo: C compiler "gcc" not found 報錯安裝gccGoCompileGC