關於一個程式的binary要怎麼存放其實是很有趣的問題,我以前都沒有去想這個問題。後來當組裝工久了以後就忍不住會想知道這些。隨便想一下就有很多問題,例如:
這些問題列出來真的是「罄竹難書」,不過我想整體來說至少在Linux下面從binutils下手應該是沒錯。第一個問題應該和linker有關係。所以我先去看ld文件中的linker script,希望可以解決我的疑惑。就算和我的問題無關,至少可以留下一些中文參考資料,造福需要的朋友。
ld
是GNU linker的程式。ld
吃多個object (.o)檔或archive (.a)檔,將他們的資料relocate還有symbol reference資訊一併輸出到新的binary。link通常是compile產生binary的最後步驟。ld
在執行的時候依照Linker command language檔案描述去產生binary。ld支援不同的binary format (BFD: Binary File Descriptor)
每次link的時候,都會依照特定的命令去產生新的object檔。而這些命令就是linker script;換句話說,linker script提供一連串的命令讓linker照表操課。Linker script描述的命令有
因為每次link一定會依據linker script去link,所以當ld
沒有指定linker script的時候,系統會使用預設的linker script。而ld --verbose
可以顯示預設的linkder script。link時指定自幹的linker script則使用ld -T 自己的linker script
。
;
分開,空白會被忽略.
可以用"
包住SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
這個抄來的範例很簡單,只有一個命令SECTIONS
。SECTIONS
是用來描述執行的時候記憶體的規劃配置(layout)。
說明這個指令細節
.
表示記憶體位置counter,起始值為0。結束值則由linker 計算把所有input section的資料整合到output section的長度。而.
如果沒有指定明確的記憶體位址的話,就會被設定為上一個位址counter的結束位址。參考示意圖: (Jim Huang) How GNU Toolchain Works投影片 。{ *(.text) }
)存放到輸出object檔案的.text
區塊中。另外要注意的是ld會自動幫你處理alignment的問題,所以不用擔心section之間的aligment問題。
ENTRY(symbol)
ENTRY(_start)
,然後去反組譯隨便一個C編譯出來的執行檔,找字串_start
可以看到裡面又去呼叫了__libc_start_main@plt
。Disassembly of section .text:
0000000000400440 <_start>:
400440: 31 ed xor %ebp,%ebp
400442: 49 89 d1 mov %rdx,%r9
400445: 5e pop %rsixpression ;
400446: 48 89 e2 mov %rsp,%rdx
400449: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40044d: 50 push %rax
40044e: 54 push %rsp
40044f: 49 c7 c0 c0 05 40 00 mov $0x4005c0,%r8
400456: 48 c7 c1 50 05 40 00 mov $0x400550,%rcx
40045d: 48 c7 c7 2d 05 40 00 mov $0x40052d,%rdi
400464: e8 b7 ff ff ff callq 400420 <__libc_start_main@plt>
400469: f4 hlt
40046a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
INCLUDE filename
filename
這個linker script。可以被放在不同的命令如SETCTION, MEMORY等。INPUT(file1 file2 ...)
GROUP(file1 file2 ...)
AS_NEEDED(file1 file2 ...)
INPUT
和GROUP
使用的命令,用來告訴linker說如果object裡面的資料有被reference到才link進來,猜測應該可以減少儲存空間。範例(未測試請自行斟酌):INPUT(file1.o file2.o AS_NEEDED(file3.o file4.o))
OUTPUT(filename)
gcc -o filename
一樣SEARCH_DIR(path)
-L path
一樣STARTUP(filename)
OUTPUT_FORMAT(bfdname)
objdump -i
列出支援的binary 檔案格式OUTPUT_FORMAT(default, big, little)
objdump -i
列出支援的binary 檔案格式TARGET(bfdname)
objdump -i
列出支援的binary 檔案格式REGION_ALIAS(alias, region)
MEMORY
命令中區塊的alias,一般來說,用在不同的平臺需要相同的memory layout時可以使用。舉例來說,當有3個平臺,記憶體layout都是相同,那麼可以
MEMORY
命令寫在個別的檔案如linkcmds.memoryINCLUDE
載入linkcmds.memory,並且直接使用alias當作一般的區塊使用。詳細的範例說明可以看這邊
INCLUDE linkcmds.memory
SECTIONS
{
.text :
{
*(.text)
} > REGION_TEXT
.rodata :
{
*(.rodata)
rodata_end = .;
} > REGION_RODATA
.data : AT (rodata_end)
{
data_start = .;
*(.data)
} > REGION_DATA
data_size = SIZEOF(.data);
data_load_start = LOADADDR(.data);
.bss :
{
*(.bss)
} > REGION_BSS
}
ASSERT(exp, message)
EXTERN(symbol1 symbol2 ...)
FORCE_COMMON_ALLOCATION
OUTPUT_ARCH(bfdarch)
objdump -i
查詢支援平臺INSERT [ AFTER | BEFORE ] output_section
SECTIONS
{
OVERLAY :
{
.ov1 { ov1*(.text) }
.ov2 { ov2*(.text) }
}
}
INSERT AFTER .text;
linker script提供設定symbol數值的方法。要注意的是,這邊的symbol可以指一個全域變數、SECTION
命令中的location counter(就是.
開頭的資料如.text
)
使用方式介紹如下:
symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= e
關於expression是三小後面會再討論。
floating_point = 0;
SECTIONS
{
.text :
{
*(.text)
_etext = .;
}
_bdata = (. + 3) & ~ 3;
.data : { *(.data) }
}
從這邊可以看到幾種assign
floating_point
的symbol為0_etext
的值為輸入object檔案.text
合體後的offset,個人猜測可以理解成end of text。(回顧一下.
是offset counter)_bdata
的值為輸出object檔案.text
結尾的offset 的4的倍數位址。這邊透露兩個資訊
HIDDEN(要隱藏的symbol)
可以把他理解成加了static
的全域變數,也就是說這個symbol只在這個處理範圍中才能摸到。PROVIDE命令(symbol = expression)
PROVIDE_HIDDEN(symbol = expression)
這節很有趣,解答我的一些小問題。
00000000004005ed g F .text 0000000000000101 main
l
: localg
: globalu
: unique global,GNU 用於ELF時的 symbol binding extenstion!
: 既是global也是localw
: weak symbol<空白>
: strong symbolC
: symbol 是一個constructor (不知道這邊constructor是指那個東西? )<空白>
: 一般 symbolW
: warning symbol (不知道是三小)<空白>
: 一般 symbolI
: 間接地reference其他的symboli
: relocate 時要處理的function<空白>
: 一般 symbolD
: dynamic symbol (不知道是三小)d
: debug symbol<空白>
: 一般 symbolF
: 這是一個functionf
: 這是一個檔案O
: 這是一個object<空白>
: 一般 symbolfoo = 100
runtime發生什麼事?
ptr = &foo
runtime發生什麼事?
foo = 100
和在程式碼中轉出的symbol如foo = 100
差別在那?
其實一開始是為了看懂這個命令才會想看linker script的。如果接觸過很小型的Embedded OS就會發現很多都是自幹linker script;而這些scripts主要的描述命令就是SETCION
。
好了,廢話少說,進入主題。SECTION命令的功用是
典型的SECTION命令長這樣子:
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。
格式為檔案(section1 section2 ...)
,檔案支援萬用字元。
所以常看到的*(.text)
的意思是:所有輸入object檔案裡面的.text
section。
指定多個section的方式有兩種