NANDFLASH啟動與標準庫問題
把u-boot的start.S移植到我的程式上,這樣程式可以用supervivi的D功能下載到記憶體中運行了,但是還不夠。因為程式在記憶體裡,如果掉電程式就沒有了,所以我們得將程式固化在flash裡面。這裡我們要固化在NandFlash裡,這就要求程式可以能夠NandFlash啟動。這裡我參考了mini2440的nandlfash讀寫程式nand.c,裡面有一個函數CopyProgramFromNand就是將Nandflash裡的程式複製到記憶體裡。在這之前我一直用u-boot預設的下載地址0x33f80000,這個是為了u-boot引導核心方便而定的,因為核心要下載到前面的記憶體中,既然我的程式沒有這個功能,那下載到0x30000000裡就可以了。把start.S中的TEXT_BASE改成0x30000000就可以了。還有在start.S裡,複製完程式判斷r0是否為零,如果不是那麼程式就會進入死迴圈。根據AAPCS,C程式返回的值就儲存在r0裡,這裡是判斷程式是否返回了0,在CopyProgramFromNand裡預設沒有傳回值,所以要在最後加上傳回值。接下來就是消除編譯錯誤。將nand.c連結到我們的程式裡,注意一定要保證在程式的前4k的位置範圍內,因為Nandflash啟動的時候,S3C2440將Nandflsh的前4K的內容複寫到晶片內部的SRAM裡,如果NandFlash的讀寫程式不在這4k的代碼裡就無法完成程式的拷貝。
編譯好的程式在我的下載資源裡:http://download.csdn.net/detail/yaozhenguo2006/3752515 Nandflash啟動進行的還算順利,修改之後下到nandflash裡,然後開發板切到nandflash啟動,程式正常運行。這裡我注意到流水燈不像下載到記憶體中啟動並執行那樣立即就運行,而是等了一段時間,說明拷貝代碼用了一些時間。接下來就是要移植mini2440內建庫函數了,對應的檔案是2440lib.c,我本來以為簡單的加到Makefile裡就會成功。可是事情不是想象的那樣簡單,當編譯的時候出現了一大堆錯誤。這些錯誤剛看起來都莫名奇妙,而且不是編譯的錯誤,都是連結的錯誤。編譯的時候沒錯說明不是語法錯誤,連結錯誤說明是庫出了問題。主要提示的錯誤資訊如下:
第一類:
2440lib.o: In function `Uart_Init':2440lib.c:(.text+0x370): undefined reference to `__aeabi_i2d'2440lib.c:(.text+0x390): undefined reference to `__aeabi_ddiv'2440lib.c:(.text+0x3a8): undefined reference to `__aeabi_i2d'2440lib.c:(.text+0x3c4): undefined reference to `__aeabi_ddiv'
在Uart_Init這個函數中,主要用了一些浮點數的運算和除法的運算,我們知道armv4指令集是沒有除法和浮點運算指令的,所以必須軟體類比。編譯的時候類比除法與浮點運算需要的庫,在連結的時候連結器沒有找到,所以出現了這個問題。為了驗證我的猜測,我把浮點數運算以及除法運算都注釋掉,錯誤提示就沒有了。說明就是沒有找到浮點運算以及除法運算的庫。
第二類:
2440lib.o: In function `Uart_GetIntNum':2440lib.c:(.text+0x994): undefined reference to `strlen'2440lib.c:(.text+0xa20): undefined reference to `atoi'2440lib.c:(.text+0xa5c): undefined reference to `__ctype_b_loc'2440lib.c:(.text+0xa90): undefined reference to `__ctype_b_loc'2440lib.o: In function `Uart_Printf':2440lib.c:(.text+0xd90): undefined reference to `vsprintf'
在Uart_GetIntNum這個函數中主要調用了標準C庫中的strlen和atio函數,找不到符號,說明連結的時候沒有找到標準C庫。Uart_Printf也應該是同樣的錯誤。
對於以上兩種問題都是庫的問題。既然連結器找不到相關的庫。我們就加上一定的連結選項讓他找到。在做這個之前我們先瞭解一下,gcc開發環境下程式連結的兩種方式。第一種使用gcc內建的連結功能。以arm-linux-gcc來說明,這種連結方式命令基本形式如下
$(CC) -static -Wl,-Tboot.lds,-Map,system.map -nostartfiles -o boot.elf $(objs) CC 這裡就是arm-linux-gcc -Wl,-Tboot.lds,-Map,system.map -Wl表示後面的參數是傳遞給連結器的,參數以逗號分割。-Tboot.lds 是指定的連結指令碼,規划了程式在記憶體中的位置 -Map,system.map 這是連結後產生的符號表 -nostartfile 不讓連結器加入預設啟動代碼,如果不加這個選項,連結器就會預設加入一個啟動代碼,因為我們要連結自己的啟動代碼,當然不需要它預設的了。 -o boot.elf 最後產生的elf格式的檔案。 $(objs) 我們編譯的.o檔案
用以上形式連結程式,arm-linux-gcc會調用collect2連結器,這個連結器有一定的預設選項,比如預設尋找庫的路徑,預設連結的庫。通過給arm-linux-gcc 加上-v 選項,我們可以查看他的預設行為。現在以我的開發環境為例,連結的時候會有如下的輸出:
/home/sun/study/crosstools/4.4.3/bin/../libexec/gcc/arm-none-linux-gnueabi/4.4.3/collect2 從這個輸出中,我們可以發現,arm-linux-gcc調用了collect2。--sysroot=/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root -Bstatic -dynamic-linker /lib/ld-linux.so.3 -X -m armelf_linux_eabi -o boot.elf sysroot暫時沒有發現是做什麼的,-Bstatic 很顯然是靜態連結 -L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3 -L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc -L/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3/../../../../arm-none-linux-gnueabi/lib -L/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root/lib -L/home/sun/study/crosstools/4.4.3/bin/../arm-none-linux-gnueabi//sys-root/usr/lib 以上就是collect2預設尋找的庫路徑 -Tboot.lds -Map system.map 傳遞過來的連結器參數start.o lowlevel_init.o nand.o interrupt.o main.o 2440lib.o print.o 要連結的.o檔案 --start-group -lgcc -lgcc_eh -lc --end-group 這個選項很重要,連結器連結的靜態庫。-lc 代錶鏈接標準c庫, -lgcc 代表要連結libgcc.a,這個庫應該是gcc擴充的庫。
下面說一下另外一種連結方式,就是用ld命令來連結,這裡就是arm-linux-ld,這種方式也就是我連結出錯的連結方式。這種連結方式,沒有預設的參數,尋找庫的路徑以及連結庫都要自己添加。之所以連結出錯就是因為我沒有指定連結的庫以及尋找庫的路徑。既然如此,那麼使用第一種連結方式連結我的程式就應該是沒錯的。我將Makefile修改了一下再make,還是有錯,令人欣喜的就是錯誤減少了,就只剩下如下的這一種錯誤:
/home/sun/study/crosstools/4.4.3/bin/../lib/gcc/arm-none-linux-gnueabi/4.4.3/libgcc_eh.a(unwind-arm.o): In function `get_eit_entry':/opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/libgcc/../gcc/config/arm/unwind-arm.c:673: undefined reference to `__exidx_end'/opt/FriendlyARM/mini2440/build-toolschain/working/src/gcc-4.4.3/libgcc/../gcc/config/arm/unwind-arm.c:673: undefined reference to `__exidx_start'
經過分析我認為是Uart_Printf函數裡調用vsprintf的緣故,將Uart_Printf注釋掉,然後再make。連結就通過了,說明就是這個vsprintf的問題。放下這個問題先不管,我試一下ld這種連結方式,在加上尋找庫的路徑以及要連結的庫名的情況下是否可以成功make。修改Makefile如下:
CC = arm-linux-gccLD = arm-linux-ldOBJCOPY = arm-linux-objcopyobjs := start.o lowlevel_init.o main.o nand.o 2440lib.oboot.bin: $(objs) $(LD) -Bstatic -Tboot.lds -Ttext 0x33F80000 $(objs) \ -L/home/sun/study/crosstools/4.4.3/lib/gcc/arm-none-linux-gnueabi/4.4.3 \ -L/home/sun/study/crosstools/4.4.3/arm-none-linux-gnueabi/sys-root/usr/lib \ -Map boot.map -o boot.elf --start-group -lgcc -lgcc_eh -lgcov -lc --end-group $(OBJCOPY) -O binary boot.elf boot.bin%.o:%.c $(CC) -Wall -c -o $@ {1}lt;%.o:%.S $(CC) -Wall -c -o $@ {1}lt;clean: rm -f *.bin *elf *.o
用以上的Makefile,make通過了。程式下載到板子上現象也是正確的。這說明ld連結器正確連結了程式。現在兩種連結方式都可以正確的連結程式了。那麼就剩下一個問題,就是Uart_Printf函數裡vsprintf。這個函數是一個格式化字串轉換函式,應該是標準C庫裡的函數,在連結器能夠找到c庫的情況下,兩種連結方式都提示同樣的錯誤。我猜測vsprintf這裡面還調用了其他庫的函數,我找了很久也沒有找到到底調用了什麼庫。列印函數對於程式調試是必須的。既然vsprintf不能使用,那麼就自己實現一個。參考u-boot的printf的實現,我自己編寫一了一個vsprintf,然後結合Uart_SendString實現了串口列印功能。
簡單的說一下實現printf的方法,首先要解決函數可變參數的問題,printf(char *fmt,...)顯然是一個可變參數函數,第一個參數為字串,後面是格式化輸出參數列表。c語言中函數的參數都是壓進棧裡的,可變參數函數必須有一個參數表示參數的個數,才能讓編譯器知道要壓進棧多少參數,以及函數返回時彈出多少參數,printf(char *fmt,...)實現這個功能的就是fmt字串,裡面有多少'%',就代表後面有多少個參數,所以我們必須提取出fmt字串中'%'的個數,以及針對'%'後面不同的字元來處理參數。printf實作類別似如下:
void myprintf(char *fmt,...){ va_list ap; char string[256]; va_start(ap,fmt); myvsprintf(string,fmt,ap); Uart_SendString(string); va_end(ap);}
va_list 其實就是*char類型,va_list ap 也就是開始定義了一個char類型的指標變數ap。 va_start是一個宏,作用就是取得fmt指標地址,並跳過這個地址賦值給ap,這樣ap就指向了除了fmt指標的第一個可變參數在記憶體中的地址,然後通過myvsprintf(string,fmt,ap)對fmt字串結合可變參數進行轉化,並把轉化的結果賦值給string,最後通過Uart_SendString()函數將字串發送給串口。具體實現可以看我的原始碼。在我的資源裡 http://download.csdn.net/detail/yaozhenguo2006/3774535
標準C庫的問題就算暫時解決了,不過還是給我提了一個醒,在arm-linux-gcc上開發裸機程式,如果用到標準C庫一定要注意,不是所有的函數都可以使用,比如vsnprintf就不能使用。其他還有什麼函數不能使用還是個未知數,所以盡量不用標準C庫的函數才是保證程式安全的辦法。典型的例子就是u-boot,它就沒有使用標準c庫,所用的相關函數都是自己實現的,這樣就保證u-boot很強的可移植性。也許arm-elf-gcc會沒有這個問題,畢竟連結的是ulibc庫,專門針對嵌入式的,以後有機會一定要驗證一下。