首先,記住一句話:程序的鏈接地址必須等於運行地址!
在學習exynos 4412的啟動過程時,發現自己對鏈接地址的作用不是很了解,於是上網查找了資料做了基本了解,在此做個總結。
上圖是exynos 4412啟動時iROM、BL1和BL2在iRAM中的分布情況。由圖中可以看出,BL2會被加載到0x020_3400的起始地址處(由BL1加載),照理來說,在BL2代碼編譯鏈接過程中應該將鏈接地址指定到0x0202_3400才是正確的,但是在實際做led實驗(此處BL2的代碼功能即是按鍵控制led亮滅)過程中發現,將鏈接地址指定為0x0202_0000也照常運行。以下為鏈接腳本的內容:
SECTIONS
{
. = 0x02020000; #注:此處正確的鏈接地址應該為0x0202_3400
.text : { *(.text) }
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data*) }
.bss ALIGN(4) : { *(.bss) *(COMMON) }
}
要想明白原因,就得理解鏈接地址的作用。
先看鏈接器的作用:
當鏈接器進行鏈接的時候,首先決定各個目標文件在最終可執行文件里的位置。然后訪問所有目標文件的地址重定義表,對其中記錄的地址進行重定向 (加上一個偏移量,即該編譯單元在可執行文件上的起始地址)。然后遍歷所有目標文件的未解決符號表,並且在所有的導出符號表里查找匹配的符號, 並在未解決符號表中所記錄的位置上填寫實現地址。最后把所有的目標文件的內容寫在各自的位置上,再作一些另的工作,就生成一個可執行文件。
在上述鏈接腳本中指定的鏈接地址,即是告訴鏈接器將程序的起始地址設置為0x0202_0000,由此,鏈接器會根據這個設定的起始地址設定程序中位置相關代碼的地址。假設鏈接地址是0x0202_0000,而程序的起始地址實際是在0x0202_3400,當運行位置無關代碼時(即要跳轉的運行地址為當前PC的值加上一個相對偏移量),CPU可以正確找到程序的存放位置,因此運行不會出錯。但是如果存在位置相關代碼(即鏈接時指定好的固定具體地址,非當前PC值加相對偏移量獲得的相對地址),那么當CPU跳轉到對應的運行地址處運行時,由於實際的代碼存放地址與運行地址(運行地址由鏈接時根據鏈接地址計算得到)不同,那么運行就會出錯了。
舉個例子:假設當將鏈接地址指定為0x0202_0000,由此生成的可執行程序中有條位置相關代碼是 goto 0x0202_1000 ,而實際可執行程序存放在0x0202_3400的起始地址處,那么實際 goto 0x0202_1000 對應的代碼就不在0x0202_1000處了,而CPU通過執行 goto 0x0202_1000 跳轉到0x0202_1000處,到那里去運行代碼,卻找不到對應的代碼,因此程序運行就出錯了。
如果鏈接地址與程序在內存中的實際存放起始地址不同,當沒有位置相關代碼時程序運行不會出錯,這就是前面說的led程序可以正常運行的原因。但是如果程序中有位置相關代碼,那么當運行到位置相關代碼時,便會發生錯誤。
因此,程序的鏈接地址必須等於運行地址。