前言
最近MIPS上開發一個程序,需要用到浮點運算。
寫好bootloader,main函數,在main函數調用log浮點運算,包含math庫。
然后再寫好makefile,ld腳本。
gcc的參數用到了:
CFLAGS= -c -march=3081 -msoft-float -fno-inline $(ENDIAN) -G0
ld的參數用到了:
LDFLAGS= -march=3081 -msoft-float -nostartfiles -lgcc -lm -lc -Wl,-Map,rlx_test.map
庫的鏈接順序
本文的重點是講述gcc庫的鏈接順序。
剛開始的時候,在鏈接參數部分,我的順序是這么安排的: -lc -lgcc -lm。
結果compile正常,但是在ld的時候,遇到問題了,總是報log函數找不到errno變量。
經過仔細分析,發現我的程序本身是有問題的,運算表達式可以總結為:log(a)。
結果a為0了,log函數會報錯,會將錯誤代碼賦給__errno這個全局變量。而__errno變量是聲明在errno.h頭文件中,但是定義在libc的某個file中。
gcc庫的鏈接順序,官方是這么解釋的:
https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Link-Options.html#Link-Options
gcc -l 解釋如下:
-l library
Search the library named library when linking. (The second alter-
native with the library as a separate argument is only for POSIX
compliance and is not recommended.)
It makes a difference where in the command you write this option;
the linker searches and processes libraries and object files in the
order they are specified. Thus, foo.o -lz bar.o searches library z
after file foo.o but before bar.o. If bar.o refers to functions in
z, those functions may not be loaded.
這句話翻譯過來的意思就是說,如果你的庫在鏈接時安排的順序是:foo.o -lz bar.o。那么gcc的鏈接器先搜索庫foo,然后是z庫,然后是bar庫。
這樣就帶來一個問題,如果庫bar調用了庫z里面的函數,但是鏈接器是先搜索的庫z,這時候並沒有發現庫bar調用庫z啊,所以就不會把庫z中的這部分函數體挑出來進行鏈接。
而是只把庫z中,被foo庫調用的函數體挑出來。
回到我們之前的描述。
由於__errno全局變量是定義在庫libc中,而庫libm中的log函數會訪問__errno全局變量。這就要求鏈接時,libm在libc的前面。所以我們應該這樣安排庫的鏈接順序:
-lm -lgcc -lc
一句話,越是被別人調用的越底層的庫,就越放在后面;越是調用別人的越上層的庫,就越放在前面。
庫的說明
下面我們來講講libm,libgcc, libc分別是做什么用的:
libm庫,是數學運算函數的庫,里面包含了各種基本的數學函數實現,例如sin,cos,平方根,log等。
libgcc庫,要想用gcc編譯代碼就需要調用的庫,里面包含了一些基本的函數。參考https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html。例如部分整形/浮點運算,異常處理,一些雜項函數。
libc庫,是c的標准庫,里面包含了基本輸入輸出函數,memcpy之類,string copy之類的函數。
從這些庫的用途,我們就知道為什么要按照-lm -lgcc -lc 的順序調用了。
當然在編譯內核和一些庫時,我們可能會看到nostdlib,這個選項,意思是不調用標准庫libc和libgcc,而是要我們自己去實現這些基本的庫函數被內核函數調用。
一些編譯和鏈接參數的說明
-march=3081,是指定cpu架構是MIPS 3081。
-msoft-float,是針對沒有FPU單元的CPU,編譯浮點運算代碼需要的,也叫軟浮點。
-nostartfiles,是指不用默認的bootload代碼,而要用我們自己寫啟動引導代碼。