ARM Architecture C 語言定址解析——
從U-Boot relocation所展開的探索(二)
by LazyCatDesign www.lazycatdesign.com
ARM Architecture C語言PIC定址方式解析
承前文所述,可不可以產生一種可以運行在任意位址區段的代碼呢。可以,這種代碼被稱之為Position-Independent Code,簡稱PIC(windows DLL,Linux Share Object,這兩者就是典型的PIC檔案)。那麼如何產生PIC呢。可以通過為編譯器指定編譯選項產生,比如:
arm-none-eabi-gcc -c -o -fpic main.o main.c
又比如:
arm-none-eabi-gcc -c -o -fpie main.o main.c
這樣編譯產生的目標檔案包含了PIC所需要的資訊, -fpic,-fpie是gcc的PIC編譯選項。ld也有PIC串連選項 -pie,要獲得一個完整的PIC可運行檔案,串連目標檔案時必須為ld指定-pie選項,比如:
arm-none-eabi-ld -Tarm_pic.lds main.o -o arm_pic -pie
PIC可運行檔案的一個最重要特點就是——這種檔案裡包含一個Global Offset Table,簡稱GOT。每一個GOT Entry記錄了一個對象的地址(對象可以是全域變數或函數),CPU從GOT中讀取GOT Entry從而獲得全域變數的地址。
下面討論指定了 -fpic編譯選項和 -pie串連選項所產生的代碼是如何定址的。
命令列進入arm_pic目錄,make GCC_PIC=-fpic LD_PIC=-pie,得到以下檔案:arm_pic(elf格式檔案)
arm_pic.bin(二進位鏡像檔案) arm_pic.dump(反組譯碼檔案) arm_pic.map(Memory Map檔案)
從arm_pic.dump可見,出現了.got資料區段,這一份GOT包含6個Entry,基地址為0x402001d0,6個Entry標示出6個全域變數的地址。
接下來通過main函數分析PIC定址,main函數反組譯碼代碼如下圖所示:
與上一篇文章分析的彙編代碼不同,現在Lable裡面存放的已經不是地址值,而是位移量(offset)。global_var1的定址經過如下3個步驟(global_str的定址方式也是如此): r3通過Lable1取得GOT Base相對pc的offset,從而確定GOT Base的地址; r2通過Lable2取得global_var1 GTO Entry相對於GOT Base的offset; r2 + r3累加(Base + offset)得到global_var1 GTO Entry的地址,從而取得global_var1的地址;
每一個變數的地址最終都是從GOT中獲得,這就是PIC的定址方式,也是它的核心,得到GOT的基地址,就能修改變數的地址從而對變數進行relocation(重定位)。
OK,跟上一篇文章所討論一樣,我們把arm_pic整體copy到0x80000000,同時,將GOT中每一個entry的內容累加上位移量0x3fe00000,這樣所有C全域變數的地址都被調整到新的地址(修改變數地址的這一操作被稱之為relocation),main函數和foo函數中的變數定址不會出錯了,那麼程式是不是就能正常運行了呢。還不行,為什麼。GOT中儲存的僅僅是C的變數地址和函數地址,但不要忘了,我們的工程不僅僅有C代碼,還有彙編代碼。那麼彙編代碼的對象是如何relocation呢。我們如何得到彙編代碼中需要relocation的對象資訊呢。答案在.rel.dyn資料區段和.dynsym資料區段,這部分將在下一篇文章中分析,同時也將具體分析U-Boot2011.12如何relocation。