FreeBSD連載(57):可執行程序格式

1999年12月29日 15:01 王波

可執行程序格式

  在FreeBSD下的可執行程序通常可分為兩類,一類為使用各種解 釋語言編寫的腳本,如sh、awk、perl、Tcl等,這些程序需要解釋程序 進行解釋執行,小巧方便,對於實現不常使用、不要求效率的程序非常 有用﹔另一類就是使用C等高級語言編譯後產生的可執行二進制程序。

  Unix之所以功能強大,原因之一就在於它提供了強大的再開發 能力。這不僅與提供了高級語言C的編譯器有關,而且也與提供了很多 種能以解釋方式執行的簡單腳本語言有關。解釋程序腳本的特點是方便 性、簡單靈活,而且也比較容易學習入手。很多情況下,需要完成的工 作任務功能比較單一,並不需要頻繁運行,而且要求快速編寫出來,這 就適合使用解釋型語言編寫,並且解釋程序本身就具備處理文本和字符 串的便捷性,並能夠和很多現有程序通過系統提供的管道、環境變量等 方式結合起來,使得它們非常適合實現文本處理功能。

  解釋語言的缺點是每次運行程序時都要載入語言的解釋器,解釋 執行程序,因而效率較低,並且不能直接操縱內存和I/O設備,不適合編 寫大型程序和對效率要求較高的場合。

  每個解釋腳本程序的第一行指出該腳本程序使用的解釋器,例如 一個普通的shell程序的第一行為:

#! /bin/sh

  不同的解釋語言可用在不同的方面,最常用的有shell解釋程序, 依據使用shell的不同,也分為不同的shell腳本,基本上也分為sh和csh 兩種不同的風格。系統管理中經常使用shell程序來執行一些日常管理任 務,很多軟體也使用shell程序來提供輔助安裝和設置任務。perl也是一 種常用的、功能強大的解釋語言,它兼有解釋性程序的方便性和高級編 程語言的強大功能,使程序員能在很短的時間內寫出非常有效的程序。 因此perl得到了眾多程序員的支持,通過為perl開發了更多的程序模塊 ,進一步使得perl的處理能力變得更為強大。目前perl已經成為了最流 行的一種解釋語言,尤其在編寫Web伺服器上的CGI程序方面,更是處於 無可爭議的地位。Tcl/tk是另一種解釋語言,它能用在X Window系統下 ,使用描述語言顯示不同的X控件,因此很多X應用程序使用它來建立自 己的圖形接口。

  • 二進制執行程序

  使用高級語言編寫、並經過編譯得到的二進制執行程序執行效率 更高,並且只有二進制格式的執行檔案才能充分利用Unix系統提供的全部 功能。同樣系統核心也是一個特殊格式的二進制執行檔案。

  早期的Unix使用a.out格式作為它的執行檔案格式,隨著Unix的發 展,又出現了其他幾種執行檔案的格式,目前最重要的執行檔案格式為ELF 格式,採用這種格式的最初想法是為了在不同平台間採用相同的執行檔案 格式,並實現動態共享連接庫。雖然ELF檔案格式並沒有達到AT&T最 初設想的全部目的,但這種檔案格式卻成為最流行的執行檔案格式。除此 之外,實際使用的檔案格式還有一種較老的COFF格式,這種格式是在Unix System V R3.2中使用的,目前只有老版本的SCO Unix中還在使用它,而 SCO也正逐漸轉向ELF格式。

  FreeBSD可以同時支持這兩種執行檔案的格式,FreeBSD 2.2.x之前 的版本使用a.out格式作為預設的執行檔案格式,到FreeBSD 3.x之後ELF格 式成為預設的執行檔案格式,並且以後會徹底轉向ELF。事實上在FreeBSD 下的a.out格式具備了相當多的特性,如動態連接等ELF格式具備的特性, 也有一些ELF格式不具備的特性,如壓縮執行檔案格式。但由於FreeBSD中 使用的編譯器gcc決定不再支持a.out格式的緣故,因此FreeBSD也必須轉向 ELF格式。這也是目前還支持a.out格式的FreeBSD版本預設使用較老版本編 譯器的原因之一。

  在FreeBSD中,a.out格式的執行檔案可以支持壓縮執行格式,這使 得使用gzip壓縮過的a.out格式的執行檔案也能立即執行,目前還不能對ELF 格式的檔案提供這種支持。

  FreeBSD的檔案格式從aout到ELF的轉變是漸變的,首先是在3.0-RELEASE 中將執行程序的預設格式轉變為ELF格式,核心檔案還保持aout格式,直至 FreeBSD-3.1,全部執行檔案格式才預設設置為ELF格式。

  轉向ELF也造成很多相關程序的轉變,如原有的Boot Loader不支持 ELF格式的核心,3.1-RELEASE就升級到新Boot Loader﹔而原有的可加載模 塊lkm為aout格式,也需要轉向ELF格式的modules。新可加載模塊的位置為 /modules目錄,並使用kldload、klduload、kldstat來進行管理。(aout格 式的模塊管理命令為modload、modstat和modunload)。

  a.out和ELF格式使用的庫檔案也是不同的,使用ELF執行檔案格式的 FreeBSD 3.x中,/usr/lib下為ELF格式的函數庫,而用一個子目錄/usr/lib/aout 存放a.out格式的函數庫,用於兼容2.2.x之前版本的FreeBSD程序。但這給一 些使用包裝技術的軟體(一些中文外掛系統)造成了一些小麻煩。對不同格 式的執行檔案要使用不同的包裝庫,系統不會將與程序本身格式不同的連接 庫連接到程序上,對應的錯誤信息為 “bad magic”,指出檔案格式的不同。

  由於3.x之後的預設格式為elf格式,為了生成a.out格式的檔案,必須 在編譯和連接時使用 -aout參數,告訴編譯器gcc和連接器ld使用不同的格式 生成執行檔案。

  • 靜態連接和動態連接

  在操作系統發展的早期,除了核心提供的接口,所有的庫函數都要 連接到程序中,這樣所有的程序都可以直接在系統核心下運行。然而事實上 大部分程序都會使用一些相同的庫函數,尤其是在使用高級語言編程的時候 ,通常都使用同樣的庫。例如,C語言編寫的程序通常都使用printf函數進行 輸出,使用scanf讀入用戶輸入內容。如果每個庫函數都連接到用戶程序中, 這樣每個程序都會包括這個函數的一個拷貝,就浪費了內存空間。

  因此,現代操作系統使用動態連接的技術,不將常用的庫直接編譯進 每個程序中,而是保留相應的接口,在核心載入程序時,再使用動態連接程 序將庫載入並和執行程序連接起來。這就是動態連接的技術,由於庫和程序 是分別載入的,因此多個程序可以共享一個庫的同一個拷貝,節約了資源。

  不論對於a.out格式還是ELF格式,FreeBSD均支持動態連接,因此應用 程序預設就使用動態連接的方式。如果想使用靜態連接,可以在應用程序編譯 連接時,指定-static連接選項,將目標程序連接成靜態連接的執行檔案。由於 庫代碼被連接進執行檔案中,靜態連接的執行檔案要比動態連接的執行檔案要 大。

$ cc -static -o a1 hello.c
$ cc -o a2 hello.c
$ ls -l  a1 a2
-rwxr-xr-x  1 wb  wheel  45017 Apr 18 16:26 a1
-rwxr-xr-x  1 wb  wheel   2540 Apr 18 16:27 a2

  在FreeBSD下,共享庫被放到/etc/ld-config設定的目錄下,通常為 /usr/lib,每個庫檔案使用.so和庫的版本號結尾。例如,libc.so.3.1為一個 標準C庫函數的動態共享庫檔案。對於a.out格式的執行檔案,其動態庫檔案位 於/usr/lib/aout目錄下。

  可以使用程序ldd來確定一個程序使用的動態連接庫:

bash-2.02$ ldd /usr/bin/vi
/usr/bin/vi:
        libcurses.so.2 => /usr/lib/libcurses.so.2 (0x2808e000)
        libtermcap.so.2 => /usr/lib/libtermcap.so.2 (0x2809a000)
        libc.so.3 => /usr/lib/libc.so.3 (0x2809f000)

  • 其他系統的執行檔案

  很多其他Unix系統,例如BSD/OS和Linux,也是運行在Intel平台上的 系統,那麼執行程序中的處理器指令是完全相同的,不同之處只在於應用程序 的格式、應用程序與操作系統的接口、庫檔案等。事實上由於同為Unix系統, 這些差異也很小,因此通過調整核心的一些參數設置,FreeBSD完全可以直接 運行這些系統下的執行程序。

  FreeBSD能夠同BSD/OS、NetBSD和OpenBSD的Intel平台上的應用程序相 兼容,同為BSD家族的成員,他們非常類似。NetBSD、OpenBSD和FreeBSD同為 免費系統,並且具有同樣的起源,與FreeBSD的關系非常密切,因此FreeBSD能 直接運行NetBSD和OpenBSD的Intel平台下的執行程序。然而NetBSD和OpenBSD 也是自由操作系統,因此它們中的應用程序也會有相應的FreeBSD版本,因此這 個功能一般很少用到。BSDI是一個商業公司,因此會提供BSD/OS下的二進制執 行檔案,但不提供原始碼。FreeBSD能夠完美的運行BSD/OS下的a.out格式的執 行檔案,ELF執行格式的程序也能執行,但偶爾會有問題發生,因此就需要調整 系統設置。

  FreeBSD也能夠執行SCO Unix的執行檔案,這需要使用核心的ibcs2( Intel binary compatibility system 2)選項。這需要載入一個核心可加載 模塊,這需要使用root身份執行ibcs2命令以載入ibcs2模塊。

# ibcs2

  可以在rc.conf中設置 “ibcs2_enable=YES” ,使開機後立即載入這 個模塊。

  但是要執行SCO的應用程序,僅有核心支持還是不夠的,還需要有SCO Unix的函數庫。但SCO Unix的庫函數是SCO Unix的一部分,受版權保護的。如 果使用者擁有合法的SCO共享庫和應用程序,就可以運行SCO Unix上的大型商業 應用程序。

  同樣,FreeBSD也能夠運行Linux的可執行程序,與執行SCO程序類似, 這也要求核心支持並載入相應的模塊。rc.conf中的相應參數為 “linux_enable” 。

# linux

  但是與SCO不同的是,Linux是一種自由操作系統,其庫函數為GNU開發 的函數庫,使用GNU通用許可保護自由使用的權利。因此FreeBSD在Packages Collection中提供了Linux的共享庫,安裝了這些Linux的庫函數之後,就可以 執行Linux的執行程序了。

# pkg_add /cdrom/packages/All/linux_lib-2.6.tgz

  FreeBSD也能執行Solaris x86和SCO Unixware的ELF格式執行程序,只 是這個功能還沒有加入正式發行版本。同樣,這也需要相應的系統動態連接庫 檔案,但是這些庫是商業產品,因此使用起來就會受到限制。

  • 運行Linux應用程序

  在運行Linux應用程序時,會遇到一些程序不能正確執行的情況,很多 情況下並不是FreeBSD本身的問題,而是由於系統的設置不正確造成的,必須作 一些設置和調整。

  由於Linux使用Elf檔案格式,有些時候FreeBSD不能分清一個ELF執行檔案 是那種系統的執行檔案,而誤認為是FreeBSD的執行檔案,導致不能正確執行程 序。此時就需要使用brandelf命令來指定需要執行的ELF執行檔案的類型,Linux 的ELF執行檔案為Linux:

$ brandelf -t Linux linux_application

  另一種差異是系統檔案名字的差異,例如Linux的加密密碼檔案為shadow, 而FreeBSD使用master.passwd,Linux的控制台終端設備檔案為/dev/tty0,FreeBSD 為/dev/ttyv0,對於一些要使用這些系統檔案的應用程序,就造成無法正確讀取相 應檔案的問題,這種情況可以使用符號連接來解決。

# cd /etc
# ln -s master.passwd shadow
# cd /dev
# ln -s ttyv0 tty0

  此外,Linux動態連接庫的版本不合適,也是很多Linux程序不能正常運行 的主要原因。Linux使用GNU開發的glib作為庫函數,編譯器gcc首先實現了glibc1 ,用於兼容標準的C庫函數libc5。隨著gcc的發展,它發展到了glibc 2,也被稱 為libc6。這兩個不同版本的庫有一定的差異,glib2功能更強,但由於出現時間 還短,不是所有的Linux版本都使用glibc2。有的Linux版本還在使用glibc1,如 Slackware 3.6,而另一些Linux如RedHat 5.0,Debin 2.0都使用glibc2,因此運 行Linux應用程序的時候,首先就要確認其使用的C庫函數版本。除了標準的C庫之 外,Linux應用程序使用的其他動態連接庫也會存在不一致的問題。

  因此如果Linux應用程序使用的庫和FreeBSD安裝的Linux共享庫版本不一 致,那麼就不會正常執行這些Linux程序。Linux是一個非常活躍的開發系統,某 些版本的Linux常常等不及系統穩定就將應用程序及其使用的庫升級,因此為了運 行要求新動態連接庫的Linux應用程序,就必須及時更新Linux的共享庫。除了及 時更新FreeBSD正式發行的Linux共享庫(這個共享庫通常為一個穩定版本,不會 一味求新)之外,為了跟上Linux升級的腳步,還必須手工安裝Linux的庫和其他 執行檔案。

  目前FreeBSD提供的Linux動態連接庫為Linuxlib-2.6,提供了對glibc2庫 及其他一些共享庫的支持,然而眾多的應用程序還會使用其他各種動態連接庫。 FreeBSD的軟體庫只能支持常用的Linux應用程序,為了徹底解決Linux應用程序的 動態連接庫更新問題,並不能依靠FreeBSD提供的軟體包。還是應該使用Linux的 方式,按照Linux的方式安裝所需要的Linux動態連接庫。

  FreeBSD下用於與Linux相兼容的程序和庫檔案統統位於/usr/compat/linux 目錄下(根目錄下有它的一個連接/compat/linux,compat目錄為兼容其他系統執 行檔案的目錄),為了安裝新的Linux檔案,首先要將這個目錄清除。

# cd /usr/compat/linux
# rm -fr *

  安裝新的Linux兼容檔案可以通過多種方式來完成,由於目前最流行的 Linux系統通常使用RPM的包管理方式,因此可以使用RPM包來安裝Linux檔案。這 需要首先安裝包管理程序rpm,這個程序有FreeBSD版本(此時還無法運行Linux程 序)。可以在Packages Collection中找到這個軟體。

  然後就要為安裝Linux的RPM包初始化rpm,使rpm不使用根目錄作為系統的 起始路徑,而使用/usr/compat/linux作為所有的rpm包的根進行安裝,這就要使 用 --root參數指定相對的根目錄,以將Linux執行程序與FreeBSD執行執行程序 區分開。rpm在/usr/compat/linux/var/local/lib/rpm中記錄包的安裝信息。

# mkdir -p /usr/compat/linux/var/local/lib/rpm

# rpm --initdb --dbpath /usr/compat/linux/var/local/lib/rpm

# rpm -i --ignoreos --root /usr/compat/linux ld.so-1.9.5-7.i386.rpm

# rpm -i --ignoreos --root /usr/compat/linux ldconfig-1.9.5-3.i386.rpm

# rpm -i --ignoreos --root /usr/compat/linux glib-1.0.1-3.i386.rpm

# rpm -i --ignoreos --root /usr/compat/linux glibc-2.0.7-17.i386.rpm

  當使用rpm -I安裝好所有必須的系統支持檔案,以及需要執行的應用程序, 這樣安裝完畢之後,在通過調整一些參數,Linux程序就能正常執行了。主要需要根 據應用程序的錯誤提示,修改FreeBSD和Linux在檔案和目錄結構上的不同,如Linux 執行程序的位置,應該更改為/usr/compat/linux/usr/bin目錄或其他相應的路徑, 而非原有根目錄下的路徑,以使得Linux程序內部只調用Linux執行程序。只有一些 對執行程序的格式不敏感的程序,可以使用/bin或/usr/bin下的相應FreeBSD執行程 序。通過這樣的設置之後,在FreeBSD下就能運行包括Oracle 8 for Linux在內的大 型應用軟體。

  但是FreeBSD並不可能運行所有的Linux程序,有的應用程序程序對Linux核心 有一定要求,需要核心的特定版本的支持。一般來講,對於這些對Linux特定核心有要 求的應用程序,FreeBSD也就無法提供支持了。

  不管怎樣,雖然在FreeBSD下運行Linux執行程序沒有太大的問題,然而畢竟 Linux是一個獨立的操作系統,處理問題的風格與FreeBSD不同,因此除了較小的程序 之外,安裝支持檔案、必要的目錄結構等參數的調整是免不了的。