版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
【移動應用開發(fā)技術】LinuxKernel編譯和鏈接中的linkerscript語法是怎樣的
這篇文章將為大家詳細講解有關LinuxKernel編譯和鏈接中的linkerscript語法是怎樣的,文章內(nèi)容質(zhì)量較高,因此在下分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。先要講講這個問題是怎么來的。我在編譯內(nèi)核的時候,發(fā)現(xiàn)arch/arm/kernel目錄下有一個這樣的文件:vmlinux.lds.S。第一眼看上去,想想是不是匯編文件呢?打開一看,好像不是。那它是干嘛的?而且前面已經(jīng)說過,make
V=1的時候,發(fā)現(xiàn)這個文件的用處在ld命令中,即ld-Tvmlinux.lds.S,好像是鏈接命令用的,如下所示如arm-linux-ld-EL-p--no-undefined-X--build-id-ovmlinux-Tarch/arm/kernel/vmlinux.lds。manld,得到-T的意思是:為ld指定一個Linkerscript,意思是ld根據(jù)這個文件的內(nèi)容來生成最終的二進制。也許上面這個問題,你從沒關注過,但是在研究內(nèi)核代碼的時候,常常有地方說“
__init宏會在最后的模塊中生成一個特定的section,然后kernel加載的時候,尋找這個section中的函數(shù)”,說白了,上面這句話就是說最后生成的模塊中,有一個特定的section,這又是什么東西?好吧,希望上面的問題勾起你的好奇心。下面我們來掃盲,最后會給一個鏈接地址,各看官可以去那深造。一section是什么?好吧,我們需要解釋一下平時編譯鏈接生成的二進制可執(zhí)行程序(比如說ELF,EXE也行),so或者dll,內(nèi)核(非壓縮的,參加本系列第一節(jié)內(nèi)容、vmlinux),或者ko是怎么組織的。其實,大家或多或少都知道這些二進制中包括有什么text/bss/data節(jié)(也叫section)。text節(jié)存儲的是代碼、data存儲的是已經(jīng)初始化的靜態(tài)變量、bss節(jié)存儲的是未初始化的什么東西...上面的東西我就不細究了。反正一點,一個二進制,最終會包含很多section。那么,為什么section叫text/bss/data,能叫別的名字嗎?OK,可以。但是你得告訴ld,那么這些內(nèi)容就通過-T選項指定一個linkerscript就行了。這些內(nèi)容我們放到后面的實例中來介紹。(再三強調(diào),咱們在理論上只是拋磚引玉,希望有興趣的看官自己研究,注意和我們分享你的成果就行了。)二linkscript基礎知識介紹linkerscript中的語法是linkercommandlanguage(很簡單的language,大家不用害怕...)。那么LS的目的是什么呢?LS描述輸入文件(也就是gcc
-c命令產(chǎn)生的.o文件即object文件)中的section最終如何對應到一個輸出文件。這個其實好理解,例如一個elf由三個.o文件構(gòu)成,每個.o文件都有text/data/bss段,但最終的那一個elf就會將三個輸入的.o文件的段合并到一起。好了,下面我們介紹一些基本知識:ld的功能是將input文件組裝成一個output文件。這些文件內(nèi)部的都有特殊的組織結(jié)構(gòu),這種結(jié)構(gòu)被叫做objectfileformat。每一個文件叫做object
file(這可能就是.o文件的來歷吧。哈哈),輸出文件也叫可執(zhí)行文件(an
executable),但是對于ld來說,它也是一種object文件。那么Object文件有什么特殊的地方呢?恩,它內(nèi)部組織是按照section(段、或者節(jié),以后不再區(qū)分二者)來組織的。一句話,object文件內(nèi)部包含段每個段都有名字和size。另外,段內(nèi)部還包含一些數(shù)據(jù),這些數(shù)據(jù)叫做section
contents,以后稱段內(nèi)容。每個段有不同的屬性。例如text段標志為可加載(loadable),表示該段內(nèi)的contents在運行時候(當然指輸出文件執(zhí)行的時候)需要加載到內(nèi)存中。另外一些段中沒有contents,那么這些段標示為allocatable,即需要分配一些內(nèi)存(有時候這些內(nèi)存會被初始化成0,這里說的應該是BSS段。BSS段在二進制文件中沒有占據(jù)空間,即磁盤上二進制文件的大小比較小,但是加載到內(nèi)存后,需要為BSS段分配內(nèi)存空間。),還有一些段屬于debug的,這里包含一些debug信息。既然需要加載到內(nèi)存中,那么加載到內(nèi)存的地址是什么呢?loadable和allocable的段都有兩個地址,VMA:虛擬地址,即程序運行時候的地址,例如把text段的VMA首地址設置為0x800000000,那么運行時候的首地址就是這個了。另外還有一個LMA,即Loadmemory
address。這個地址是section加載時的地址。暈了吧?二者有啥區(qū)別?一般情況下,VMA=LMA。但也有例外。例如設置某數(shù)據(jù)段的LMA在ROM中(即加載的時候拷貝到ROM中),運行的時候拷貝到RAM中,這樣LMA和VMA就不同了?!泛茈y搞懂不是?這種方法用于初始化一些全局變量,基于那種ROM
based
system。(問一個問題,run的時候,怎么根據(jù)section中的VMA進行相應設置啊??以后可能需要研究下內(nèi)核中關于execve實現(xiàn)方面的內(nèi)容了)。關于VMA和LMA,大家通過objdump-h選項可以查看。三簡單例子下面來一個簡單例子,
SECTIONS
{
.=0x10000;
.text:{*(.text)}
.=0x8000000;
.data:{*(.data)}
.bss:{*(.bss)}
}SECTIONS是LS語法中的關鍵command,它用來描述輸出文件的內(nèi)存布局。例如上例中就含text/data/bss三個部分(實際上text/data/bss才是段,但是SECTIONS這個詞在LS中是一個command,希望各位看官要明白)。.=0x10000;其中的.非常關鍵,它代表locationcounter(LC)。意思是.text段的開始設置在0x10000處。這個LC應該指的是LMA,但大多數(shù)情況下VMA=LMA。.text:{*(.text)},這個表示輸出文件的.text段內(nèi)容由所有輸入文件(*)的.text段組成。組成順序就是ld命令中輸入文件的順序,例如1.obj,2.obj此后,由來了一個.=0x800000000;。如果沒有這個賦值的,那么LC應該等于0x10000+sizeof(text段),即LC如果不強制指定的話,它默認就是上一次的LC+中間section的長度。還好,這里強制指定LC=0X800000000.表明后面的.data段的開始位于這個地址。.data和后面的.bss表示分別有輸入文件的.data和.bss段構(gòu)成。你看,我們從這個LC文件中學到了什么?恩,我們可以任意設置各個段的LMA值。當然,絕大部分情況,我們不需要有自己的LS來控制輸出文件的內(nèi)存布局。不過LK(linuxkernel)可不一樣了四霸王硬上弓vmlinux.lds.S分析OK,有了上面的基礎知識,下面我們霸王硬上弓,直接分析arch/arm/kernel/vmlinux.lds.S.雖然最終鏈接用的是vmlinux.lds,但是那個文件由vmlinux.lds.S(這是一個匯編文件)得到,arm-linux-gcc-E-Wp,-MD,arch/arm/kernel/.vmlinux.lds.d-nostdinc
-D__KERNEL__-mlittle-endian-DTEXT_OFFSET=0x00008000-P-C-Uarm-D__ASSEMBLY__-oarch/arm/kernel/vmlinux.lds
arch/arm/kernel/vmlinux.lds.S所以,我們直接分析vmlinux.lds好了。/*
一堆注釋,這里就不再貼上了,另外,增加//號做為注釋標識
*ConvertaphysicaladdresstoaPageFrameNumberandback
*///OUTPUT_ARCH是LS語法中的COMMAND,用來指定輸出文件的machinearch。objdump-f可查詢所有支持的machine。另外//這些東西涉及到一種叫BFD的。各位看官可以自己搜索下BFD的內(nèi)容。//下面這表示輸出文件基于ARM架構(gòu)OUTPUT_ARCH(arm)
//ENTRY也是一個command,用來設置入口點。這里表示入口點是stext
。根據(jù)LD的描述,入口點的意思就是程序運行的第一條指令。內(nèi)核是一個模塊,大家把他想象//成一個運行在硬件上的大程序就可以了。而我們的程序又是運行在內(nèi)核至上的。比較下Java虛擬機以及運行在其上的Java程序吧ENTRY(stext)//設置jiffies為jiffies_64jiffies=jiffies_64;//定義輸出文件的段SECTIONS{//設置locationcount為0xc0008000,這個好理解吧?內(nèi)核運行的地址全在C0000000以上
.=0xC0000000+0x00008000;//定義一個.text.head段,由輸入文件中所有.text.head段組成/*LS語法中,關于seciton的定義如下:section[address][(type)]:
[AT(lma)][ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
}[>region][AT>lma_region][:phdr:phdr...][=fillexp]其中,address為VMA,而AT命令中的為LMA。一般情況,address不會設置,所以它默認等于當前的locationcounter*/
.text.head:{/*這個非常關鍵,咱們在內(nèi)核代碼中經(jīng)常能看到一些變量聲明,例如externint__stext,但是卻找不到在哪定義的其實這些都是在lds文件中定義的。這里得說一下編譯鏈接相關的小知識。咱們這知道大概即可,具體內(nèi)容可以自己深入研究假設C代碼中定義一個變量intx=0;那么1編譯器首先會分配一塊內(nèi)存,用來存儲該變量的值2編譯器在程序的symbol表中,創(chuàng)建一項,用來存儲這個變量的地址例如,上面的intx=0,就在symbol表中創(chuàng)建一x項,這個x項指向一塊內(nèi)存,sizeof(int)大小,存儲的值為0。當有地方使用這個x的時候,編譯器會生成相應的代碼,首先指向這個x的內(nèi)存,然后讀取內(nèi)存中的值。上面的內(nèi)容是C中一個變量的定義。但是Linkerscript中也可以定義變量,這時候只會生成一個symbol項,但是沒有分配內(nèi)存。。例如_stext=0x100,那么會創(chuàng)建一個symbol項,指向0x100的內(nèi)存,但該內(nèi)存中沒有存儲value。所以,我們在C中使用LS中定義的變量的話,只能取它的地址。下面是一個例子:start_of_ROM
=
.ROM;
end_of_ROM
=
.ROM
+
sizeof
(.ROM)
-
1;
start_of_FLASH
=
.FLASH;上面三個變量是在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);注意其中的取地址符號&。C代碼中只能通過這種方式來使用LS中定義的變量.start_of_ROM這個值本身是沒有意義的,只有它的地址才有意義。因為它的值沒有初始化。地址就指向.ROM段的開頭。說白了,LS中定義的變量其實就是地址,即_stext=0x100就是C代碼中的一個地址int*_stext=0x100。明白了?最終的ld中會分配一個slot,然后存儲x的地址。也就是說,ld知道這些勾當。那么當然我們在LS中也可以定義一個變量,然后在C中使用了。所以下面這句話實際上定義了一個_stext變量。在C中通過extern就可以引用了。但是這里有一個比較關鍵的問題。C中定義的x=0,其值被初始化為0了。也就是slot...待補充*/
_stext=.;.
_sinittext=.;
*(.text.head)
}//定義.init段,由所有的.init.text/.cpuinit.text/.meminit.text組成//這時的LC的值為.init的開始
.init:{/*Initcodeanddata
*/
*(.init.text)*(.cpuinit.text)*(.meminit.text)//定義一個變量_einitext,它的值為當前的LC,即.init的初值+*(.init.text)*(.cpuinit.text)*(.meminit.text)的大小。也就是說變量//_einitext標示一個結(jié)尾。
_einittext=.;//下面這個變量__proc_info_begin標示一個開頭
__proc_info_begin=.;
*(..init)
//所有..init段內(nèi)容在這
__proc_info_end=.;//下面這個變量__proc_info_end標示結(jié)尾,它和__proc_info_begin變量牢牢得把輸出文件..init的內(nèi)容卡住了。//有了上面begin和end的介紹,后面就簡單了,大部分都是一個begin+end來卡住一段內(nèi)容。根據(jù)前面的介紹,begin和end又可以在C程序中引用//也就是我們通過Begin+end,就可以獲得卡住的內(nèi)容了。例如我們把一些初始化的函數(shù)指針放到一個begin和end中。然后通過一個循環(huán),不就是//可以調(diào)用這些函數(shù)了么。最后我們就來個例子介紹下。
__arch_info_begin=.;
*(..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)__early_initcall_end=.;
*(.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,表示對齊,即這里的LocationCounter的位置必須按32對齊
__initramfs_start=.;
//ramfs的位置
usr/built-in.o(.init.ramfs)
__initramfs_end=.;
.=ALIGN(4096);//4K對齊
__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=.;
}//DISACARD是一個特殊的section,表示符合這個條件的輸入段都不會寫到輸出段中,也就是輸出文件中不包含下列段
/DISCARD/:{/*Exitcodeanddata
*/
*(.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)容//ADDR為內(nèi)置函數(shù),用來返回VMA的/*這里舉個小例子,大家看看VMA和LMA到底有什么作用SECTIONS
{
.text0x1000:{*(.text)_etext=.;}
/.text段的VMA為0x1000,而且LMA=VMA
.mdata0x2000://.mdata段的VMA為0x2000,但是它的LMA卻在.text段的結(jié)尾
AT(ADDR(.text)+SIZEOF(.text))
{_data=.;*(.data);_edata=.;}
.bss0x3000:
{_bstart=.;*(.bss)*(COMMON);_bend=.;}
}看到了么?.mdata段運行的時候在0x2000,但是數(shù)據(jù)load地址卻在.text段后,所以運行的時候需要把.mdata段內(nèi)容拷貝過去。
externchar_etext,_data,_edata,_bstart,_bend;
char*src=&_etext;
//_etext為.text端的末尾VMA地址,但同時也是.mdata段LMA的開始,有LS種的AT指定
char*dst=&_data;
//_data為mdata段的VMA,現(xiàn)在需要把LMA地址開始的內(nèi)容拷貝到VMA開始的地方
/*ROMhasdataatendoftext;copyit.*/
while(dst<&_edata)
*dst++=*src++;
//拷貝明白了?不明白的好好琢磨
/*Zerobss.*/
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)容
_edata_loc=__data_loc+SIZEOF(.data);
.bss:{
__bss_start=.;/*BSS
*/
*(.bss)
*(COMMON)
_end=.;
}
/*Stabsdebuggingse
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五年度房屋財產(chǎn)分割及共有權轉(zhuǎn)讓協(xié)議3篇
- 課程設計游標卡尺
- 2025年銷售年終工作總結(jié)參考(三篇)
- 二零二五年度創(chuàng)業(yè)投資對賭協(xié)議書范本及退出機制
- 二零二五年度建筑水暖消防工程監(jiān)理分包合同2篇
- 市場總監(jiān)主要職責模版(2篇)
- 課程設計醫(yī)療急救
- 2025年牛津上海版高二地理下冊階段測試試卷
- 2025年人教版七年級物理上冊階段測試試卷
- 2025版頂樓物業(yè)買賣合同書3篇
- YY/T 0882-2013麻醉和呼吸設備與氧氣的兼容性
- JJG 596-2012電子式交流電能表
- GB/T 5237.1-2017鋁合金建筑型材第1部分:基材
- GB/T 2317.1-2000電力金具機械試驗方法
- 供應商ROHs審核表
- 五大發(fā)電公司及所屬電廠列表及分部精編版
- 小學數(shù)學聽課記錄 精選(范文20篇)
- 住宅工程公共區(qū)域精裝修施工組織設計(217頁)
- 光伏電站設備監(jiān)造與性能驗收
- 10kV架空線路施工方案
- 2018江蘇蘇州中考英語真題解析
評論
0/150
提交評論