其實一開始是為了看懂這個命令才會想看linker script的。如果接觸過很小型的Embedded OS就會發現很多都是自幹linker script;而這些scripts主要的描述命令就是SETCIONS
命令。
好了,廢話少說,進入主題。SETCIONS命令命令的功用是
SECTIONS命令長這樣子:
SECTIONS
{
sections-command
sections-command
...
}
望文生義地猜測可以這樣理解: 輸出object有一些大方向的規範,並且分為不同的section,每個section有他自己的規範。
而sections-command
可以分為下面幾種功能
要注意的事,如果你自幹的linker script沒有描述輸出object檔案的setcion的話,linker會
section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align) | ALIGN_WITH_INPUT]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
其中output-section-command
的功能有
這邊很多術語需要先搞清楚,先列出來,希望之後可以看到解答
address是section的一個optional欄位,使用的記憶體空間為VMA。如果沒有指定的話,linker會依下面的方式設定輸出object檔案section 的VMA。該VMA會遵循section 的alignment規範。
region
的話就從region內剩餘空間開始位址MEMORY
命令定義硬體記憶區塊的話,從定義的區塊中挑第一個符合SECTION的區塊。再將address設成該區塊內剩餘空間開始位址address欄位因為可以使用exression所以可能有下面的陷阱
.text . : { *(.text) }
.text : { *(.text) }
這兩個差一個.
,意義就差很多。沒有.
那個,表示沒有設定address,所以就是設成locale counter,並且linker會保證alignment。而有.
的就表示hardcode成locale counter,所以有可能會有alignment的問題。另外一點要注意的設定後locale counter也會跟著改變。
這部份可以說是整個output-section-command
的重點,目的是告訴linker讀取輸入object檔案後,怎麼把這些檔案裡面的section複製到輸出object檔案裡面適當地section。
輸入object檔案的section描述可以被分為下面幾個部份
格式為檔案(section1 section2 ...)
,檔案支援萬用字元。
所以常看到的*(.text)
的意思是:所有輸入object檔案裡面的.text
section。
指定多個section的方式有兩種
你也可以根據flag區分object檔案的section,範例如下
SECTIONS {
.text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }
.text2 : { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }
}
望文生義可以看到上面的規範就是
.text
section flag有SHF_MERGE 和 SHF_STRINGS的,請放在輸出object檔案的.text
section.text
section flag沒有SHF_WRITE的,請放在輸出object檔案的.text2
section你如果對於範例中的flag有興趣,可以看這邊, 這邊,還有這邊。我目前還不想看就是了。
另外指定輸入object檔案部份,除了指定單獨的輸入object檔案,還可以指定archieve (如libwen.a, libc.a)裡面的object檔案,用法如下 archive:file
,隨便猜一個範例libc.a:fprintf.o
支援 *
:任何長度的任何字元 ?
:單一任何字元 []
:單一字元有效的範圍如[a-z]
指小寫英文字母 \
:接下來的字元不是萬用字元,如\*
由於linker複製section的方式是多個條件滿足的話,選第一個條件滿足就處理,所以配合萬用字元可能會產生意想不到的錯誤,範例如下
.data : { *(.data) }
.data1 : { data.o(.data) }
由於複製section的方式是第一個條件滿足就處理,所以會造成data.o的.data
section放字輸出object檔案的.data
section而不是.data1
section。手冊提供了建議處理方式,有興趣的可以參考。
.bss { *(.bss) *(COMMON) }
也就是說,最後沒特別狀況,就把輸入object 檔案的COMMON section放在.bss
section。
KEEP(要保留的section)
:因為linker有garbage collection,如果要保證section不會被回收,可以用該指令。SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
長度單位命令(expression)
BYTE
:1 byteSHORT
:2 bytesLONG
:4 bytesQUAD
:8 byte以下的命令將會佔 5 bytes,第一個byte後面4個bytes將用來存放addr (如果我英文沒看錯的話,原文是store the byte 1 followed by the four byte value of the symbol addr':)。
BYTE(1)LONG(addr)`
關於64-bit的目前沒心情看,跳過。
至於endian的部份,如果輸出的object檔案有規範,則依該規範存放,否則則遵守第一個讀入的輸入object檔案。
FILL(expression)
:section內沒使用的空間將被填入expression計算後的數字。同樣效果的命令是[=fillexp]
,忘記這是啥嗎?我也忘了,所以回去找了一下為什麼要丟掉?原因是在設定輸出section的script有提到特定的section,但是link完畢後發現所有輸入object檔案都沒有該section的symbol。最後就是把這些section丟掉。
另外一個情況是輸入object檔案有/DISCARD/
既然就說要丟了就恭敬不如從命了。
還記得前面的格式嘛?再複習一下:
section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align) | ALIGN_WITH_INPUT]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
前面篇幅已經說明瞭section
,address
,以及output-section-command
等語法和命令,我們接著要介紹其他部份如下
有支援
NOLOAD
DSECT
COPY
INFO
OVERLAY
基本上type繼承輸入object中的type,不過你要硬上就是在輸出object檔案描述,範例如下。該範例顯示ROM 區段起始位址為0,並且在該section執行程式不要載入到記憶體。
SECTIONS {
ROM 0 (NOLOAD) : { ... }
...
}
前情回顧
設定輸出object檔案的VMA是在address欄位
中指定。請比對section描述格式的address
。
而LMA就是section描述格式的AT(lma)
和AT>lma_region
這兩個部份了。這兩個指令是optional的。他們的差別是:
AT(lma)
中間的lma是透過expression算出來的lma位址AT>lma_region
是指定MEMORY
裡面描述的region如果你的section沒有指定LMA的話,linker會使用下面的規則決定LMA
address欄位
中指定VMA,則LMA = VMA來點範例,這是一個嵌入式系統,假設所有的資料都放在唯讀記憶體中。那麼會發生什麼事呢?那就是你的i++
就GG了,所以要把變數部份還有其他需要寫入的部份放在RAM中,所以這個script顯示了
SECTIONS
{
.text 0x1000 : { *(.text) _etext = . ; }
.mdata 0x2000 :
AT ( ADDR (.text) + SIZEOF (.text) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
當然事情沒那麼簡單,這邊只有講layout。在沒有OS幫你搞定的時候什麼事都要自己來,所以你還要自己把有初始化的全域變數一個一個搬到RAM裡面如下。請仔細比對變數和script的symbol。另外如果有興趣看CMSIS(Cortex Microcontroller Software Interface Standard)的source code也可以看到類似的行為。
extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;
/* ROM has data at end of text; copy it. */
while (dst < &_edata)
*dst++ = *src++;
/* Zero bss. */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0;
請使用ALIGN
,或是使用ALIGN_WITH_INPUT
將讀入的object檔案中的section設定成你要的alignment。
請使用SUBALIGN
去指定輸入object檔案單一個section的alignment。
ONLY_IF_RO
ONLY_IF_RW
使用>MEMORY_指令_宣告的region
範例,把.text
放ROM section,該section位址是在硬體rom記憶體區塊。
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
PHDR 是ELF的program header縮寫,又稱為segment。當ELF loader載入ELF執行檔的時候,會看這些segment決定要如何把讀入的檔案放在記憶體中,這部份和ABI有關係,按下不表,等我那天心情好再來看ELF和ABI。
section描述格式中phdr
的用法是
範例如下
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
前面FILL講到指令填空的資料。而section描述格式中=fillexp
也有相同效果,範例如下
SECTIONS { .text : { *(.text) } =0x90909090 }
Overlay是一種在記憶體小於執行檔案時的技巧。其基本概念就是
對應到linker script就會格式這樣
OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]
{
secname1
{
output-section-command
output-section-command
...
} [:phdr...] [=fill]
secname2
{
output-section-command
output-section-command
...
} [:phdr...] [=fill]
...
} [>region] [:phdr...] [=fill]
OVERLAY命令中除了OVERLAY
和section 名稱
以外其他都是optional。另外要注意的是OVERLAY
命令不允許region和address的描述。而在OVERLAY
最後面的資料固定為OVERLAY起始位址
+ 最大section的size
由於OVERLAY就是動態切換並執行不同section,所以在VMA的位址會固定。這表示所有的section的VMA會相同。為了方便,linker會把所有OVERLAY
中的section串接成連續的空間。
OVERLAY用法如下
「人肉」搬移表示我們需要
這部份linker會自動幫我們加入symbol,規則如下,很容易望文生義所以就不解釋了。
__load_start_section_名稱
__load_stop_section_名稱
那麼現在看一下手冊上面的範例
OVERLAY 0x1000 : AT (0x4000)
{
.text0 { o1/*.o(.text) }
.text1 { o2/*.o(.text) }
}
還記得address和AT命令嗎?一個是指定VMA另外一個是指定LMA。所以上面的設定白話文就是
.text0
和.text1
兩個section.text0
裡面放的是o1目錄下所有object檔案中的.text
.text1
裡面放的是o2目錄下所有object檔案中的.text
那麼人肉搬移要怎麼處理呢?手冊列出如下
extern char __load_start_text1, __load_stop_text1;
memcpy ((char *) 0x1000, &__load_start_text1,
&__load_stop_text1 - &__load_start_text1);
可以看到,我們說要從LMA搬到VMA,LMA的位址就由symbol內容提供。
另外手冊這個section我跳過一些東西,有興趣的朋友可以去超級比一比。