Linux Kernel編譯和鏈接中的linker script語法詳解
先要講講這個(gè)問題是怎么來的。(咱們?cè)诜治鲆粋€(gè)技術(shù)的時(shí)候,先要考慮它是想解決什么問題,或者學(xué)習(xí)新知識(shí)的時(shí)候,要清楚這個(gè)知識(shí)的目的是什么)。
我在編譯內(nèi)核的時(shí)候,發(fā)現(xiàn)arch/arm/kernel目錄下有一個(gè)這樣的文 件:vmlinux.lds.S。第一眼看上去,想想是不是匯編文件呢?打開一看,好像不是。那它是干嘛的?而且前面已經(jīng)說過,make V=1的時(shí)候,發(fā)現(xiàn)這個(gè)文件的用處在ld命令中,即ld -T vmlinux.lds.S,好像是鏈接命令用的,如下所示
如arm-linux-ld -EL -p --no-undefined -X --build-id -o vmlinux -T arch/arm/kernel/vmlinux.lds。man ld,得到-T的意思是:為ld指定一個(gè)Linker script,意思是ld根據(jù)這個(gè)文件的內(nèi)容來生成最終的二進(jìn)制。
也許上面這個(gè)問題,你從沒關(guān)注過,但是在研究內(nèi)核代碼的時(shí)候,常常有地方說“ __init宏會(huì)在最后的模塊中生成一個(gè)特定的section,然后kernel加載的時(shí)候,尋找這個(gè)section中的函數(shù)”,說白了,上面這句話就是 說最后生成的模塊中,有一個(gè)特定的section,這又是什么東西?
- LS描述輸入文件(也就是gcc -c命令產(chǎn)生的.o文件即object文件)中的section最終如何對(duì)應(yīng)到一個(gè)輸出文件。這個(gè)其實(shí)好理解,例如一個(gè)elf由三個(gè).o文件構(gòu)成,每 個(gè).o文件都有text/data/bss段,但最終的那一個(gè)elf就會(huì)將三個(gè)輸入的.o文件的段合并到一起。
- ld的功能是將input文件組裝成一個(gè)output文件。這些文件內(nèi)部的都有特殊 的組織結(jié)構(gòu),這種結(jié)構(gòu)被叫做object file format。每一個(gè)文件叫做object file(這可能就是.o文件的來歷吧。哈哈),輸出文件也叫可執(zhí)行文件(an executable),但是對(duì)于ld來說,它也是一種object文件。那么Object文件有什么特殊的地方呢?恩,它內(nèi)部組織是按照 section(段、或者節(jié),以后不再區(qū)分二者)來組織的。一句話,object文件內(nèi)部包含段......
- 每個(gè)段都有名字和size。另外,段內(nèi)部還包含一些數(shù)據(jù), 這些數(shù)據(jù)叫做section contents,以后稱段內(nèi)容。每個(gè)段有不同的屬性。例如text段標(biāo)志為可加載(loadable),表示該段內(nèi)的contents在運(yùn)行時(shí)候(當(dāng)然 指輸出文件執(zhí)行的時(shí)候)需要加載到內(nèi)存中。另外一些段中沒有contents,那么這些段標(biāo)示為allocatable,即需要分配一些內(nèi)存(有時(shí)候這些 內(nèi)存會(huì)被初始化成0,這里說的應(yīng)該是BSS段。BSS段在二進(jìn)制文件中沒有占據(jù)空間,即磁盤上二進(jìn)制文件的大小比較小,但是加載到內(nèi)存后,需要為BSS段 分配內(nèi)存空間。),還有一些段屬于debug的,這里包含一些debug信息。
- 既然需要加載到內(nèi)存中,那么加載到內(nèi)存的地址是什么 呢?loadable和allocable的段都有兩個(gè)地址,VMA:虛擬地址,即程序運(yùn)行時(shí)候的地址,例如把text段的VMA首地址設(shè)置為 0x800000000,那么運(yùn)行時(shí)候的首地址就是這個(gè)了。另外還有一個(gè)LMA,即Load memory address。這個(gè)地址是section加載時(shí)的地址。暈了吧?二者有啥區(qū)別?一般情況下,VMA=LMA。但也有例外。例如設(shè)置某數(shù)據(jù)段的LMA在 ROM中(即加載的時(shí)候拷貝到ROM中),運(yùn)行的時(shí)候拷貝到RAM中,這樣LMA和VMA就不同了。---------》很難搞懂不是?這種方法用于初始 化一些全局變量,基于那種ROM based system。(問一個(gè)問題,run的時(shí)候,怎么根據(jù)section中的VMA進(jìn)行相應(yīng)設(shè)置啊??以后可能需要研究下內(nèi)核中關(guān)于execve實(shí)現(xiàn)方面的內(nèi) 容了)。關(guān)于VMA和LMA,大家通過objdump -h選項(xiàng)可以查看。
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
- SECTIONS是LS語法中的關(guān)鍵command,它用來描述輸出文件的內(nèi)存布局。例如上例中就含text/data/bss三個(gè)部分(實(shí)際上text/data/bss才是段,但是SECTIONS這個(gè)詞在LS中是一個(gè)command,希望各位看官要明白)。
- .=0x10000; 其中的.非常關(guān)鍵,它代表location counter(LC)。意思是.text段的開始設(shè)置在0x10000處。這個(gè)LC應(yīng)該指的是LMA,但大多數(shù)情況下VMA=LMA。
- .text:{*(.text)},這個(gè)表示輸出文件的.text段內(nèi)容由所有輸入文件(*)的.text段組成。組成順序就是ld命令中輸入文件的順序,例如1.obj,2.obj......
- 此后,由來了一個(gè).=0x800000000;。如果沒有 這個(gè)賦值的,那么LC應(yīng)該等于0x10000+sizeof(text段),即LC如果不強(qiáng)制指定的話,它默認(rèn)就是上一次的LC+中間section的長 度。還好,這里強(qiáng)制指定LC=0X800000000.表明后面的.data段的開始位于這個(gè)地址。
- .data和后面的.bss表示分別有輸入文件的.data和.bss段構(gòu)成。
一堆注釋,這里就不再貼上了,另外,增加//號(hào)做為注釋標(biāo)識(shí)
* Convert a physical address to a Page Frame Number and back
*/
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0xC0000000 + 0x00008000;
[AT(lma)] [ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
.text.head : {
start_of_ROM = .ROM; end_of_ROM = .ROM + sizeof (.ROM) - 1; start_of_FLASH = .FLASH;
上面三個(gè)變量是在LS中定義的,分別指向.ROM段的開始和結(jié)尾,以及FLASH段的開始?,F(xiàn)在在C代碼中想把ROM段的內(nèi)容拷貝到FLASH段中,下面是C代碼:
extern char start_of_ROM, end_of_ROM, start_of_FLASH; memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);
_stext = .;.
_sinittext = .;
*(.text.head)
}
.init : { /* Init code and data */
*(.init.text) *(.cpuinit.text) *(.meminit.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init) //所有.proc.info.init段內(nèi)容在這
__proc_info_end = .;//下面這個(gè)變量 __proc_info_end標(biāo)示結(jié)尾,它和__proc_info_begin變量牢牢得把輸出文件.proc.info.init的內(nèi)容卡住了。
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN(16);
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.initcallearly.init)
*(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .;
. = ALIGN(32);//ALIGN,表示對(duì)齊,即這里的Location Counter的位置必須按32對(duì)齊
__initramfs_start = .; //ramfs的位置
usr/built-in.o(.init.ramfs)
__initramfs_end = .;
. = ALIGN(4096); //4K對(duì)齊
__per_cpu_load = .;
__per_cpu_start = .;
*(.data.percpu.page_aligned)
*(.data.percpu)
*(.data.percpu.shared_aligned)
__per_cpu_end = .;
__init_begin = _stext;
*(.init.data) *(.cpuinit.data) *(.cpuinit.rodata) *(.meminit.data) *(.meminit.rodata)
. = ALIGN(4096);
__init_end = .;
}
/DISCARD/ : { /* Exit code and data */
*(.exit.text) *(.cpuexit.text) *(.memexit.text)
*(.exit.data) *(.cpuexit.data) *(.cpuexit.rodata) *(.memexit.data) *(.memexit.rodata)
*(.exitcall.exit)
*(.ARM.exidx.exit.text)
*(.ARM.extab.exit.text)
}
//省略部分內(nèi)容
{
.text 0x1000 : { *(.text) _etext = . ; } /.text段的VMA為0x1000,而且LMA=VMA
.mdata 0x2000 : //.mdata段的VMA為0x2000,但是它的LMA卻在.text段的結(jié)尾
AT ( ADDR (.text) + SIZEOF (.text) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
char *src = &_etext; //_etext為.text端的末尾 VMA地址,但同時(shí)也是.mdata段LMA的開始,有LS種的AT指定
char *dst = &_data; //_data為mdata段的VMA,現(xiàn)在需要把LMA地址開始的內(nèi)容拷貝到VMA開始的地方
/* ROM has data at end of text; copy it. */
while (dst < &_edata)
*dst++ = *src++; //拷貝....明白了?不明白的好好琢磨
/* Zero bss. */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0; //初始化數(shù)據(jù)區(qū)域
.rodata : AT(ADDR(.rodata) - 0) {
__start_rodata = .;
*(.rodata) *(.rodata.*) *(__vermagic) *(__markers_strings) *(__tracepoints_strings)
}
.rodata1 : AT(ADDR(.rodata1) - 0) {
*(.rodata1)
}
......//省略部分內(nèi)容
.bss : {
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
_end = .;
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}
//ASSERT是命令,如果第一個(gè)參數(shù)為0,則打印第二個(gè)參數(shù)的信息(也就是錯(cuò)誤信息),然后ld命令退出。
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
{
initcall_t *call;
//上面已經(jīng)定義成數(shù)組了,所以下面這些變量直接取的就是指針,和上面例子中使用&一個(gè)意思,反正不能用value
for (call = __early_initcall_end; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
- LK源碼中那些找不到來源的變量是怎么來的---》在LS定義。
- VMA和LMA是怎么回事。