Linux下的lds链接脚本二
生活随笔
收集整理的這篇文章主要介紹了
Linux下的lds链接脚本二
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
對于.lds文件,它定義了整個程序編譯之后的連接過程,決定了一個可執(zhí)行程序的各個段的存儲位置。雖然現(xiàn)在我還沒怎么用它,但感覺還是挺重要的,有必要了解一下。 先看一下GNU官方網(wǎng)站上對.lds文件形式的完整描述:
??? 以上,head.o放在0x00000000地址開始處,init.o放在head.o后面,他們的運行地址也是0x00000000,即連接和存儲地址相同(沒有AT指定);main.o放在4096(0x1000,是AT指定的,存儲地址)開始處,但是它的運行地址在0x30000000,運行之前需要從0x1000(加載處)復(fù)制到0x30000000(運行處),此過程也就用到了讀取Nand flash。
這就是存儲地址和連接(運行)地址的不同,稱為加載時域和運行時域,可以在.lds連接腳本文件中分別指定。
編寫好的.lds文件,在用arm-linux-ld連接命令時帶-Tfilename來調(diào)用執(zhí)行,如
arm-linux-ld?–Tnand.lds x.o y.o?–o xy.o。也用-Ttext參數(shù)直接指定連接地址,如
arm-linux-ld?–Ttext 0x30000000 x.o y.o?–o xy.o。 既然程序有了兩種地址,就涉及到一些跳轉(zhuǎn)指令的區(qū)別,這里正好寫下來,以后萬一忘記了也可查看,以前不少東西沒記下來現(xiàn)在忘得差不多了。。。 ARM匯編中,常有兩種跳轉(zhuǎn)方法:b跳轉(zhuǎn)指令、ldr指令向PC賦值。 我自己經(jīng)過歸納如下: (1)???????b step1?:b跳轉(zhuǎn)指令是相對跳轉(zhuǎn),依賴當前PC的值,偏移量是通過該指令本身的bit[23:0]算出來的,這使得使用b指令的程序不依賴于要跳到的代碼的位置,只看指令本身。 (2)???????ldr pc, =step1?:該指令是從內(nèi)存中的某個位置(step1)讀出數(shù)據(jù)并賦給PC,同樣依賴當前PC的值,但是偏移量是那個位置(step1)的連接地址(運行時的地址),所以可以用它實現(xiàn)從Flash到RAM的程序跳轉(zhuǎn)。 (3)???????此外,有必要回味一下adr偽指令,U-boot中那段relocate代碼就是通過adr實現(xiàn)當前程序是在RAM中還是flash中。仍然用我當時的注釋:
??? 下面,結(jié)合u-boot.lds看看一個正式的連接腳本文件。這個文件的基本功能還能看明白,雖然上面分析了好多,但其中那些GNU風(fēng)格的符號還是著實讓我感到迷惑,好菜啊,怪不得連被3家公司鄙視,自己鄙視自己。。。
本文中的所有代碼版本都是基于ST的SpearPlus開發(fā)板的。
xloader是在系統(tǒng)上電之后,執(zhí)行完ROM中的frimware后最先開始執(zhí)行的用戶程序,它的體積很小,執(zhí)行的功能也很簡單,主要是對系統(tǒng)時鐘以及外部SDRAM進行初始化,初始化完成之后就檢查Flash中的uboot image是否準備好,如果準備好了就將Flash中的uboot image根據(jù)image header中指定的load address加載到外部SDRAM中,然后就跳轉(zhuǎn)到uboot執(zhí)行代碼。
這里,我試圖從頭開始,在源代碼級別上來分析整個系統(tǒng)的引導(dǎo)過程。
像Xloader或者uboot之類的程序,并不像我們平常寫的應(yīng)用程序那樣,程序的入口函數(shù)直接找main函數(shù)就行。對于這種系統(tǒng)程序,在最開始看代碼,尤其是要找到最開始執(zhí)行的代碼的位置的時候,最好的一個方法就是找到整個工程的.lds文件,也就是鏈接腳本文件(linker loader script)。它定義了整個工程在編譯之后的鏈接過程,以及各個輸入目標文件中的各個段在輸出目標文件中的分布。詳細的關(guān)于lds文件的介紹可以參考 gnu的在線文檔:http://sourceware.org/binutils/docs/ld/index.html。其中的第三節(jié)Linker Script對鏈接腳本文件進行了介紹。
現(xiàn)在,我們首先開看一看xloader.lds的代碼:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(XLOADER_ENTRY)
SECTIONS
{
??? . = 0x00000000;
??? . = ALIGN(4);
??? .text??? :
??? {
????? ./obj/init.o??? (.text)
????? *(.text)
??? }
?
??? .rodata . :
??????? {
??????????????? *(.rodata)
??????? }
?
??????? . = ALIGN(4);
?
??? ?
??? .data : { *(.data) }
??? . = ALIGN(4);
??? .got : { *(.got) }
???? ?
??? . = ALIGN(4);
??? __bss_start = .;
??? .bss : { *(.bss) }
??? _end = .;
}
下面,我們對這一段代碼逐句進行分析。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
在GNU的文檔中,是這么定義的:
OUTPUT_FORMAT(default, big, little),在鏈接的時候,如果使用了-EB的命令行參數(shù),則使用這里的big參數(shù)指定的字節(jié)序,如果使用了-EL的命令行參數(shù),則使用這里的little參數(shù)指定的字節(jié)序,如果沒有使用任何命令行參數(shù),則使用這里的default參數(shù)指定的字節(jié)序。
由xloader.lds中的定義可見,不管在鏈接的時候使用了何種命令行參數(shù),輸出的目標文件都是使用elf32-littlearm方式的字節(jié)序。
OUTPUT_ARCH(arm)
在GNU的文檔中,是這么定義的:
OUTPUT_ARCH(bfdarch),也就是指定了目標的體系結(jié)構(gòu),在這里,SpearPlus內(nèi)部使用的處理器核是arm926ejs的,因此體系結(jié)構(gòu)也就是arm。
ENTRY(XLOADER_ENTRY)
在GNU的文檔中,是這么定義的:
ENTRY(symbol)
There are several ways to set the entry point. The linker will set the entry point by trying each of the following methods in order, and stopping when one of them succeeds:
??? * the `-e' entry command-line option;
??? * the ENTRY(symbol) command in a linker script;
??? * the value of the symbol start, if defined;
??? * the address of the first byte of the `.text' section, if present;
??? * The address 0.?
也就是說,ENTRY(XLOADER_ENTRY)定義了整個程序的入口處,也就是在標號XLOADER_ENTRY處。整個程序?qū)倪@里開始運行。
接下來的部分,是對整個輸出目標文件中各個段的存儲位置的定義。
在GNU的文檔中,是這么定義的:
SECTIONS
???? {
?????? sections-command
?????? sections-command
?????? ...
???? }
對于其中的每一個sections-command,其完整的定義如下:
The full description of an output section looks like this:
???? section [address] [(type)] :
?????? [AT(lma)] [ALIGN(section_align)] [SUBALIGN(subsection_align)]
?????? {
???????? output-section-command
???????? output-section-command
???????? ...
?????? } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
Most output sections do not use most of the optional section attributes.
The whitespace around section is required, so that the section name is unambiguous. The colon and the curly braces are also required. The line breaks and other white space are optional.?
下面來看看xloader.lds中SECTIONS的定義:
SECTIONS
{
?? ?/* location counter設(shè)置為0x00000000,其實由于在Makefile中的
?? ? * 鏈接選項中使用了-Ttext $(TEXT_BASE),而TEXT_BASE=0xD2800B00
?? ? * 因此,此處的設(shè)置其實是沒有作用的,代碼運行的時候?qū)⑦\行在TEXT_BASE地址
?? ? */
??? . = 0x00000000;
??? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?? ?/* 將所有輸入目標文件中的.text段即代碼段放在此處,并且,輸出目標文件中的.text段中的
?? ? * 開頭部分存放init.o的.text段。也就是運行的第一條代碼,也就是XLOADER_ENTRY
?? ? * 標號對應(yīng)的代碼就在init.o當中
?? ? */
??? .text??? :
??? {
????? ./obj/init.o??? (.text)
????? *(.text)
??? }
?
?? ?/* 緊接著.text段,存放所有輸入目標文件中的.rodata段,也就是
?? ? * 只讀數(shù)據(jù)段。此處注意.rodata后跟著的.,這個.表示當前l(fā)ocation counter,
?? ? * 對應(yīng)于上述完整描述sections中的[address]
?? ? * 此處表示.rodata段緊接著.text段存放,而不用任何對齊
?? ? */
??? .rodata . :
??????? {
??????????????? *(.rodata)
??????? }
?
??????? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?
??? ?
?? ?/* 將所有輸入目標文件中的.data讀寫數(shù)據(jù)段存儲在此處
?? ? * 所有全局手動初始化的變量存儲在該段中,并且在輸出目標文件中已經(jīng)分配了存儲空間
?? ? */
??? .data : { *(.data) }
??? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?? ?/* .got段是GLOBAL OFFSET TABLE,具體的作用還沒有搞清楚 */
??? .got : { *(.got) }
???? ?
??? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?? ?/* .bss段的開始,所有全局未初始化變量的大小等信息存儲在該段中
?? ? * 但是在輸出的目標文件中并不為這些變量分配存儲空間,
?? ? * 而是交給操作系統(tǒng)在初始化的時候分配內(nèi)存,然后緊跟在.data段后面并初始化為零
?? ? * 另外,此處還定義了兩個標號分別表示.bss的開始和結(jié)束(也是整個目標文件的結(jié)束)
?? ? */
??? __bss_start = .;
??? .bss : { *(.bss) }
??? _end = .;
}
從這里,我們能夠得到的最關(guān)鍵的信息是:整個程序的入口在標號XLOADER_ENTRY處,并且該標號定義在init.o目標文件中,因為整個最終的鏈接之后的目標文件中,位于最開頭的就是init.o目標文件。
于是,我們可以根據(jù)這個線索來繼續(xù)追蹤整個的引導(dǎo)過程了。
參考文章:
對.lds連接腳本文件的分析
http://blog.csdn.net/tony821224/archive/2008/01/18/2051755.aspx
Documentation for binutils 2.18--ld
http://sourceware.org/binutils/docs/ld/index.html
.bss段和.data段的區(qū)別
http://www.w3china.org/blog/more.asp?name=FoxWolf&id=29997
什么是bss段
http://blog.csdn.net/bobocheng1231/archive/2008/02/23/2115289.aspx
| SECTIONS?{ ... secname?start?BLOCK(align)?(NOLOAD)?:?AT?(?ldadr?) ??{?contents?}?>region?:phdr?=fill ... } |
secname和contents是必須的,其他的都是可選的。下面挑幾個常用的看看: 1、secname:段名 2、contents:決定哪些內(nèi)容放在本段,可以是整個目標文件,也可以是目標文件中的某段(代碼段、數(shù)據(jù)段等) 3、start:本段連接(運行)的地址,如果沒有使用AT(ldadr),本段存儲的地址也是start。GNU網(wǎng)站上說start可以用任意一種描述地址的符號來描述。 4、AT(ldadr):定義本段存儲(加載)的地址。 看一個簡單的例子:(摘自《2410完全開發(fā)》)
| /* nand.lds */ SECTIONS?{? firtst 0x00000000?:?{?head.o init.o?}? second 0x30000000?:?AT(4096)?{?main.o?}? } |
arm-linux-ld?–Tnand.lds x.o y.o?–o xy.o。也用-Ttext參數(shù)直接指定連接地址,如
arm-linux-ld?–Ttext 0x30000000 x.o y.o?–o xy.o。 既然程序有了兩種地址,就涉及到一些跳轉(zhuǎn)指令的區(qū)別,這里正好寫下來,以后萬一忘記了也可查看,以前不少東西沒記下來現(xiàn)在忘得差不多了。。。 ARM匯編中,常有兩種跳轉(zhuǎn)方法:b跳轉(zhuǎn)指令、ldr指令向PC賦值。 我自己經(jīng)過歸納如下: (1)???????b step1?:b跳轉(zhuǎn)指令是相對跳轉(zhuǎn),依賴當前PC的值,偏移量是通過該指令本身的bit[23:0]算出來的,這使得使用b指令的程序不依賴于要跳到的代碼的位置,只看指令本身。 (2)???????ldr pc, =step1?:該指令是從內(nèi)存中的某個位置(step1)讀出數(shù)據(jù)并賦給PC,同樣依賴當前PC的值,但是偏移量是那個位置(step1)的連接地址(運行時的地址),所以可以用它實現(xiàn)從Flash到RAM的程序跳轉(zhuǎn)。 (3)???????此外,有必要回味一下adr偽指令,U-boot中那段relocate代碼就是通過adr實現(xiàn)當前程序是在RAM中還是flash中。仍然用我當時的注釋:
| relocate:?/* 把U-Boot重新定位到RAM */ ??? adr r0,?_start?/* r0是代碼的當前位置 */? /* adr偽指令,匯編器自動通過當前PC的值算出 如果執(zhí)行到_start時PC的值,放到r0中: 當此段在flash中執(zhí)行時r0 = _start = 0;當此段在RAM中執(zhí)行時_start = _TEXT_BASE(在board/smdk2410/config.mk中指定的值為0x33F80000,即u-boot在把代碼拷貝到RAM中去執(zhí)行的代碼段的開始) */ ??? ldr r1,?_TEXT_BASE?/* 測試判斷是從Flash啟動,還是RAM */? /* 此句執(zhí)行的結(jié)果r1始終是0x33FF80000,因為此值是又編譯器指定的(ads中設(shè)置,或-D設(shè)置編譯器參數(shù)) */ ??? cmp r0,?r1?/* 比較r0和r1,調(diào)試的時候不要執(zhí)行重定位 */ |
| OUTPUT_FORMAT("elf32-littlearm",?"elf32-littlearm",?"elf32-littlearm") ??;指定輸出可執(zhí)行文件是elf格式,32位ARM指令,小端 OUTPUT_ARCH(arm) ??;指定輸出可執(zhí)行文件的平臺為ARM ENTRY(_start) ??;指定輸出可執(zhí)行文件的起始代碼段為_start. SECTIONS { ????????.?=?0x00000000?;?從0x0位置開始 ????????.?=?ALIGN(4)?;?代碼以4字節(jié)對齊 ????????.text?:?;指定代碼段 ????????{ ??????????cpu/arm920t/start.o?(.text)?;?代碼的第一個代碼部分 ??????????*(.text)?;其它代碼部分 ????????} ????????.?=?ALIGN(4)? ????????.rodata?:?{?*(.rodata)?}?;指定只讀數(shù)據(jù)段 ????????.?=?ALIGN(4); ????????.data?:?{?*(.data)?}?;指定讀/寫數(shù)據(jù)段 ????????.?=?ALIGN(4); ????????.got?:?{?*(.got)?}?;指定got段,?got段式是uboot自定義的一個段,?非標準段 ????????__u_boot_cmd_start?=?.?;把__u_boot_cmd_start賦值為當前位置,?即起始位置 ????????.u_boot_cmd?:?{?*(.u_boot_cmd)?}?;指定u_boot_cmd段,?uboot把所有的uboot命令放在該段. ????????__u_boot_cmd_end?=?.;把__u_boot_cmd_end賦值為當前位置,即結(jié)束位置 ????????.?=?ALIGN(4); ????????__bss_start?=?.;?把__bss_start賦值為當前位置,即bss段的開始位置 ????????.bss?:?{?*(.bss)?};?指定bss段 ????????_end?=?.;?把_end賦值為當前位置,即bss段的結(jié)束位置 } |
xloader是在系統(tǒng)上電之后,執(zhí)行完ROM中的frimware后最先開始執(zhí)行的用戶程序,它的體積很小,執(zhí)行的功能也很簡單,主要是對系統(tǒng)時鐘以及外部SDRAM進行初始化,初始化完成之后就檢查Flash中的uboot image是否準備好,如果準備好了就將Flash中的uboot image根據(jù)image header中指定的load address加載到外部SDRAM中,然后就跳轉(zhuǎn)到uboot執(zhí)行代碼。
這里,我試圖從頭開始,在源代碼級別上來分析整個系統(tǒng)的引導(dǎo)過程。
像Xloader或者uboot之類的程序,并不像我們平常寫的應(yīng)用程序那樣,程序的入口函數(shù)直接找main函數(shù)就行。對于這種系統(tǒng)程序,在最開始看代碼,尤其是要找到最開始執(zhí)行的代碼的位置的時候,最好的一個方法就是找到整個工程的.lds文件,也就是鏈接腳本文件(linker loader script)。它定義了整個工程在編譯之后的鏈接過程,以及各個輸入目標文件中的各個段在輸出目標文件中的分布。詳細的關(guān)于lds文件的介紹可以參考 gnu的在線文檔:http://sourceware.org/binutils/docs/ld/index.html。其中的第三節(jié)Linker Script對鏈接腳本文件進行了介紹。
現(xiàn)在,我們首先開看一看xloader.lds的代碼:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(XLOADER_ENTRY)
SECTIONS
{
??? . = 0x00000000;
??? . = ALIGN(4);
??? .text??? :
??? {
????? ./obj/init.o??? (.text)
????? *(.text)
??? }
?
??? .rodata . :
??????? {
??????????????? *(.rodata)
??????? }
?
??????? . = ALIGN(4);
?
??? ?
??? .data : { *(.data) }
??? . = ALIGN(4);
??? .got : { *(.got) }
???? ?
??? . = ALIGN(4);
??? __bss_start = .;
??? .bss : { *(.bss) }
??? _end = .;
}
下面,我們對這一段代碼逐句進行分析。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
在GNU的文檔中,是這么定義的:
OUTPUT_FORMAT(default, big, little),在鏈接的時候,如果使用了-EB的命令行參數(shù),則使用這里的big參數(shù)指定的字節(jié)序,如果使用了-EL的命令行參數(shù),則使用這里的little參數(shù)指定的字節(jié)序,如果沒有使用任何命令行參數(shù),則使用這里的default參數(shù)指定的字節(jié)序。
由xloader.lds中的定義可見,不管在鏈接的時候使用了何種命令行參數(shù),輸出的目標文件都是使用elf32-littlearm方式的字節(jié)序。
OUTPUT_ARCH(arm)
在GNU的文檔中,是這么定義的:
OUTPUT_ARCH(bfdarch),也就是指定了目標的體系結(jié)構(gòu),在這里,SpearPlus內(nèi)部使用的處理器核是arm926ejs的,因此體系結(jié)構(gòu)也就是arm。
ENTRY(XLOADER_ENTRY)
在GNU的文檔中,是這么定義的:
ENTRY(symbol)
There are several ways to set the entry point. The linker will set the entry point by trying each of the following methods in order, and stopping when one of them succeeds:
??? * the `-e' entry command-line option;
??? * the ENTRY(symbol) command in a linker script;
??? * the value of the symbol start, if defined;
??? * the address of the first byte of the `.text' section, if present;
??? * The address 0.?
也就是說,ENTRY(XLOADER_ENTRY)定義了整個程序的入口處,也就是在標號XLOADER_ENTRY處。整個程序?qū)倪@里開始運行。
接下來的部分,是對整個輸出目標文件中各個段的存儲位置的定義。
在GNU的文檔中,是這么定義的:
SECTIONS
???? {
?????? sections-command
?????? sections-command
?????? ...
???? }
對于其中的每一個sections-command,其完整的定義如下:
The full description of an output section looks like this:
???? section [address] [(type)] :
?????? [AT(lma)] [ALIGN(section_align)] [SUBALIGN(subsection_align)]
?????? {
???????? output-section-command
???????? output-section-command
???????? ...
?????? } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
Most output sections do not use most of the optional section attributes.
The whitespace around section is required, so that the section name is unambiguous. The colon and the curly braces are also required. The line breaks and other white space are optional.?
下面來看看xloader.lds中SECTIONS的定義:
SECTIONS
{
?? ?/* location counter設(shè)置為0x00000000,其實由于在Makefile中的
?? ? * 鏈接選項中使用了-Ttext $(TEXT_BASE),而TEXT_BASE=0xD2800B00
?? ? * 因此,此處的設(shè)置其實是沒有作用的,代碼運行的時候?qū)⑦\行在TEXT_BASE地址
?? ? */
??? . = 0x00000000;
??? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?? ?/* 將所有輸入目標文件中的.text段即代碼段放在此處,并且,輸出目標文件中的.text段中的
?? ? * 開頭部分存放init.o的.text段。也就是運行的第一條代碼,也就是XLOADER_ENTRY
?? ? * 標號對應(yīng)的代碼就在init.o當中
?? ? */
??? .text??? :
??? {
????? ./obj/init.o??? (.text)
????? *(.text)
??? }
?
?? ?/* 緊接著.text段,存放所有輸入目標文件中的.rodata段,也就是
?? ? * 只讀數(shù)據(jù)段。此處注意.rodata后跟著的.,這個.表示當前l(fā)ocation counter,
?? ? * 對應(yīng)于上述完整描述sections中的[address]
?? ? * 此處表示.rodata段緊接著.text段存放,而不用任何對齊
?? ? */
??? .rodata . :
??????? {
??????????????? *(.rodata)
??????? }
?
??????? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?
??? ?
?? ?/* 將所有輸入目標文件中的.data讀寫數(shù)據(jù)段存儲在此處
?? ? * 所有全局手動初始化的變量存儲在該段中,并且在輸出目標文件中已經(jīng)分配了存儲空間
?? ? */
??? .data : { *(.data) }
??? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?? ?/* .got段是GLOBAL OFFSET TABLE,具體的作用還沒有搞清楚 */
??? .got : { *(.got) }
???? ?
??? . = ALIGN(4);?? ?/* 四字節(jié)對齊 */
?? ?/* .bss段的開始,所有全局未初始化變量的大小等信息存儲在該段中
?? ? * 但是在輸出的目標文件中并不為這些變量分配存儲空間,
?? ? * 而是交給操作系統(tǒng)在初始化的時候分配內(nèi)存,然后緊跟在.data段后面并初始化為零
?? ? * 另外,此處還定義了兩個標號分別表示.bss的開始和結(jié)束(也是整個目標文件的結(jié)束)
?? ? */
??? __bss_start = .;
??? .bss : { *(.bss) }
??? _end = .;
}
從這里,我們能夠得到的最關(guān)鍵的信息是:整個程序的入口在標號XLOADER_ENTRY處,并且該標號定義在init.o目標文件中,因為整個最終的鏈接之后的目標文件中,位于最開頭的就是init.o目標文件。
于是,我們可以根據(jù)這個線索來繼續(xù)追蹤整個的引導(dǎo)過程了。
參考文章:
對.lds連接腳本文件的分析
http://blog.csdn.net/tony821224/archive/2008/01/18/2051755.aspx
Documentation for binutils 2.18--ld
http://sourceware.org/binutils/docs/ld/index.html
.bss段和.data段的區(qū)別
http://www.w3china.org/blog/more.asp?name=FoxWolf&id=29997
什么是bss段
http://blog.csdn.net/bobocheng1231/archive/2008/02/23/2115289.aspx
總結(jié)
以上是生活随笔為你收集整理的Linux下的lds链接脚本二的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 胡哥面试视频手录
- 下一篇: oracle lag使用情景,lag函数