c语言如何输出字母锥子塔,GCC连接脚本学习笔记 zz
連接腳本將我整整蒙了1天零一個(gè)上午,做了很多實(shí)驗(yàn),看了人家不少例子代碼
勉強(qiáng)能駕馭了,讓linker按照我想要的來(lái)處理,做個(gè)筆記。
1,什么叫輸入段,什么叫輸出段
不知道怎么回事,我對(duì)GCC系列的輸入和輸出兩個(gè)單詞總是進(jìn)入思維死角,很簡(jiǎn)單
就是 input section 和 output
section,這里不是說(shuō)翻譯的問(wèn)題,我覺(jué)得是一種
思考的方式的問(wèn)題。
我的問(wèn)題就是:既然叫輸入端,那輸入什么?同理,輸出的是什么?不知道其他人
不會(huì)不理解這個(gè)問(wèn)題,我自己的話是理解了不少時(shí)間了 -v-
所謂的輸出段,是指生成的文件,例如 elf 中的每個(gè)段
所謂的輸入段,是指連接的時(shí)候提供LD的所有目標(biāo)文件(OBJ)中的段。
2,lma 和 vma
lma =?load memory
address
vma =?vitual memory
address
如果有研究過(guò)ADS的估計(jì)有印象,那里有個(gè) RO BASE 和 RW BASE 和 ZI BASE,也
就是說(shuō),lma 是裝載地址,vma 是運(yùn)行地址,想搞清楚這兩個(gè)問(wèn)題,可以閱讀一下
《ARM學(xué)習(xí)報(bào)告(杜云海)》作者寫的很好,將這個(gè)問(wèn)題分析的很透澈。lma 和vma
只是GCC的叫法而已,其實(shí)原理是一樣的。
3,兩個(gè)基本架構(gòu)
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
"elf32-littlearm")
OUTPUT_ARCH(arm)
一句話,照抄......因?yàn)槲覀儧](méi)有修改的余地,都是系統(tǒng)默認(rèn)的關(guān)鍵字。第一句
指示系統(tǒng)可以有生成兩種格式,默認(rèn)是 elf32-arm,端格式是 little endian
4,ENTRY(__ENTRY)
指定入口點(diǎn),LD的手冊(cè)說(shuō),ENTRY POINT 就是程序第一條執(zhí)行的指令,但是,說(shuō)老
實(shí)話,我并不理解,因?yàn)檫@里跟我的理解矛盾了,首先,通常情況,系統(tǒng)需要一個(gè)
初始化的 STARTUP.S文件來(lái)初始化硬件,也就是 bootloader的第一階段了。那么
很自然,入口點(diǎn)需要設(shè)置在這段代碼的第一條指令中,那么正常運(yùn)行的時(shí)候從第一
條指令開(kāi)始運(yùn)行。所以這里設(shè)置了__ENTRY為入口點(diǎn),這個(gè)在匯編代碼中必須得先
聲明一下為全局,才能用,否則系統(tǒng)找不到。例如:
.global __ENTRY
但是問(wèn)題是,如果我用同樣的辦法,設(shè)置另外一個(gè)不是第一條指令的入口點(diǎn),LD并
沒(méi)有報(bào)錯(cuò),但是問(wèn)題來(lái)了,生成的文件和剛才設(shè)置入口點(diǎn)為 __ENTRY 的時(shí)候一模一
樣,這就蒙了,到底這個(gè)入口點(diǎn)是怎么回事?
記得以前ADS的時(shí)候也碰到過(guò) entry point的問(wèn)題,下載仿真的時(shí)候確實(shí)是自動(dòng)跳轉(zhuǎn)
到 entry point中運(yùn)行。
我想到的可能的原因,第一,生成 elf 文件并不是能直接用在嵌入式平臺(tái)上面裸跑
的,因?yàn)槲覀儾](méi)有操作系統(tǒng),我們不需要elf文件頭的那些指示信息提供給操作
系統(tǒng),指示系統(tǒng)怎么去加載文件,在嵌入式上面的完全沒(méi)有那個(gè)必要,只需要將實(shí)
際的代碼提取出來(lái),直接運(yùn)行就OK,也就是 objcopy的操作,所以我覺(jué)得,在裸奔
的嵌入式系統(tǒng)上面,entry point是沒(méi)有意義的,只需要指向整個(gè)代碼最開(kāi)始的指
令就OK了。
暫時(shí)我還是不能清晰的理解這個(gè)東西。先放下。以后碰到問(wèn)題再分析。
5,一個(gè)輸出段的標(biāo)準(zhǔn)格式
section [address] [(type)] : [AT(lma)]
{
output-section-command
output-section-command
...
} [>region]
[AT>lma_region] [:phdr :phdr ...]
[=fillexp]
前面也說(shuō)了,所謂的輸出段是指最終生成的文件里面的段,所以一個(gè)輸出段就可以
理解為最終文件里面的一個(gè)塊,那么多個(gè)塊合起來(lái)就是一個(gè)完成文件了。
而每個(gè)小塊又分別有什么文件來(lái)組成呢?那就是輸入段了。
我自己實(shí)際用到有下面的一些,其他暫時(shí)不會(huì)用。
section_name?vma :
AT(lma)
{
output-section-command
output-section-command
...
}
[AT>lma_region]
section_name 根據(jù)ld手冊(cè)說(shuō)是有個(gè)確定的名字,其他沒(méi)啥,自己添加一些新段也是
可以的。
默認(rèn)的4個(gè)段是必須有的
.text 代碼
.rodata 常量,例如字符串什么的
.data 初始化的全局變量
.bss?沒(méi)有初始化的全局變量
其實(shí)沒(méi)什么,可以說(shuō),都是固定的,所以一句話,照抄。
段名字后面緊跟的是 vma ,也就是這個(gè)段在程序運(yùn)行的時(shí)候的地址,例如
.text 0x30000000 :
{
*(.text)
}
表示的是代碼的運(yùn)行時(shí)地址為 0x30000000 假如你的ROM在 0x0 地址,程序放在ROM
中,那個(gè)時(shí)候程序是不能正常運(yùn)行的(位置無(wú)關(guān)代碼除外),必須將代碼COPY到VMA
也就是 0x30000000 中才能正常運(yùn)行。
至于那個(gè) AT(lma) 的關(guān)鍵字,只指示代碼連接的時(shí)候應(yīng)該放在什么地方,注意好
這個(gè)英文是 load memory address,是指程序應(yīng)該裝載在什么地方,而不是指這個(gè)段
應(yīng)該在最后生成的bin文件的位置!!!這個(gè)東西蒙騙了我,讓我郁悶了1天。elf格
式的文件里面不但包含了代碼,還包含了各種各樣的信息,例如上面說(shuō)的每個(gè)段的
lma 和vma,還有其他信息都包含在里面了。
默認(rèn)狀態(tài)下,lma 是等于當(dāng)前的vma的,例如
.text 0x30000000
:
{
*(.text)
*(.rodata)
}
.data 0x33ffff00
:
{
*(.data)
}
例如我們基本的兩個(gè)段,我們指定了.text 和.data段的vma,但是沒(méi)有指定lma,那么
lma到底應(yīng)該是多少?很簡(jiǎn)單,ld認(rèn)為當(dāng)前的lma和vma是相同的,所以lma應(yīng)該分別是
0x30000000
0x33ffff00,編譯生成的elf文件很小,但是objcopy出來(lái)的文件卻非常
巨大,達(dá)到了60多MB,這是什么問(wèn)題?
elf文件很聰明,他只是保存了信息,.text段的lma是0x30000000,那么elf就保存了
知道本程序的代碼應(yīng)該加載到 0x30000000,然后又保存了.data的lma,我們留意到
中間有很多的地址空間是空的,并沒(méi)有實(shí)際的代碼,elf怎么處理?elf只保存了兩個(gè)
地址和實(shí)際的代碼,而對(duì)于其他空間里面的代碼他并不處理,所以可以看出,最后生成
的elf文件并不大,也就100多KB而已,但是后來(lái)的OBJCOPY操作中,從elf文件中copy
出程序代碼,這下就糟了,objcopy是從最開(kāi)始的lma開(kāi)始,這里是 0x30000000一直
復(fù)制到最尾段的lma,這里是 0x33ffff00,中間沒(méi)有代碼地方全部補(bǔ)零,那么60多MB
的大bin文件就是這樣來(lái)的。
可以驗(yàn)證一下,如果手動(dòng)指定開(kāi)始的lma為0的話
.text 0x30000000 :
AT(0)
{
*(.text)
*(.rodata)
}
.data 0x33ffff00
:
{
*(.data)
}
其中.text段的lma被AT強(qiáng)制指定為0,那么objcop出來(lái)的bin文件相當(dāng)?shù)娜A麗,達(dá)到了
700多MB,為什么?都說(shuō)了,從0開(kāi)始到 0x33ffff00,中間補(bǔ)零,字節(jié)數(shù)相當(dāng)?shù)目捎^呢。
一般我們常用的做法是:
1,.data段的 lma 和 vma 都是緊跟著 .text 的,或者用ARM的說(shuō)法就是
RW段緊跟著
RO段,這樣的做法非常簡(jiǎn)單
.text 0x30000000
:
{
*(.text)
*(.rodata)
}
.data
:
{
*(.data)
}
.bss?:
{
*(.bss)
*(.COMMON)
}
只指定RO BASE,然后所有代碼都是跟著RO BASE分配,這樣非常簡(jiǎn)單。
2,.data段分離出來(lái),連接到不同的vma運(yùn)行時(shí)地址。
.text 0x30000000
:
{
*(.text)
*(.rodata)
}
.data 0x31000000 :
AT(LOADADDR(.text) +
SIZEOF(.text))
{
*(.data)
}
.bss?:
{
*(.bss)
*(.COMMON)
}
其實(shí)也不難解決,像上面的代碼那樣做就OK了,上面也分析了,如果vma不同的話,objcopy
會(huì)一直復(fù)制,這樣生成的bin文件會(huì)很大,怎么解決?很簡(jiǎn)單,手工指定 .data段的lma地址
讓 .data段的 lma 緊緊跟著 .text段的末尾,這樣生成的 bin
文件就會(huì)很漂亮,跟第一種
辦法生成的bin文件結(jié)構(gòu)一模一樣!!
AT(LOADADDR(.text) +
SIZEOF(.text))
這個(gè)指令大概解釋一下,AT 是指定lma 的,然后里面用了兩個(gè)指令 LOADADDR ,和名字一樣
這個(gè)指令是用來(lái)求 lma 地址的! SIZEOF 也就是名字那樣,求大小的
LOADADDR(.text) 求出 .text 段的 lma,注意是開(kāi)始地址
SIZEOF(.text)?求出 .text
段的大小
AT(LOADADDR(.text) +
SIZEOF(.text))?的效果就是,指定
.data段的lma在 .text段lma
的結(jié)尾處!
這里補(bǔ)充一下,還有一個(gè)指令 ADDR(.text) 這個(gè)是求vma的,不是求lma。
另外,注意一下 .bss段的lma和 .data段的 lma是一樣的,這也反映了一個(gè)實(shí)質(zhì)問(wèn)題 .bss
段
只分配運(yùn)行地址 vma,并不實(shí)際占空間的。
3,如果我想自己添加一些段,應(yīng)該怎么去實(shí)現(xiàn)?
例如我要添加一個(gè) .vector 的段,里面放的是一些數(shù)據(jù),怎么實(shí)現(xiàn)?
(1)如果在匯編代碼里面添加,那么可以新啟動(dòng)一個(gè)段
例如在 2440init.S 中添加 .vector 段
.section .text
....
....
(其他代碼)
.section?.vector?@
在這里聲明一個(gè)段,并且放連個(gè)數(shù)據(jù)
.word?0x55
.word?0xaa
匯編代碼段的開(kāi)始由?.section
聲明,接著后面的都屬于這個(gè)段,直到第二個(gè) .section 聲明
為止。
我這個(gè) .vector段是需要連接到 0x33ffff00 的,非常的特殊,那么按照前面的辦法
.text 0x30000000
:
{
*(.text)
*(.rodata)
}
.data 0x31000000 :
AT(LOADADDR(.text) +
SIZEOF(.text))
{
*(.data)
}
.bss?:
{
*(.bss)
*(.COMMON)
}
.vector 0x33ffff00 :
AT(LOADADDR(.data) + SIZEOF(.data))
{
*(.vector)
}
可以看出,形式其實(shí)是一樣,不過(guò)看一下,添加的段的lma放在 .data 段的lma的后面,前面也說(shuō)
看 .bss 和 .data的lma是一樣的,所以其實(shí)無(wú)視掉 .bss段就OK了。
(2)在C語(yǔ)言中怎么添加一個(gè)變量指定放到 .vector段
很簡(jiǎn)單,用GNU擴(kuò)展語(yǔ)法(注意了,是GNU系列工具通用而已,例如gcc,這個(gè)并不是C的標(biāo)準(zhǔn))
格式如下
unsigned int __attribute__((section(".vector")))
vec=0x9988;
定義一個(gè) vec 變量,值為 0x9988,分配在 .vector 段,編譯后用 objdump
一下查看匯編代碼
可以發(fā)現(xiàn)到
Disassembly of section .vector:
33ffff00 :
33ffff00:?00009988 ?.word?0x00009988
33ffff04:?00000055 ?.word?0x00000055
33ffff08:?000000aa ?.word?0x000000aa
看到?jīng)]有?剛才說(shuō)的在匯編代碼和C代碼里面定義的數(shù)值都被連接進(jìn)去了 .vector段了,vma也正確
最后還可以看看生成的 bin 文件,看看最后的幾個(gè)數(shù)據(jù)是不是就是 0x9988 0x55 0xaa
?這樣應(yīng)該
就理解了整個(gè)連接的過(guò)程了吧?
4,MEMORY 命令在指定lma中的使用
每個(gè)段都要用 AT 來(lái)指定具體的位置,其實(shí)挺煩的,我們有更加簡(jiǎn)單的辦法,我們定義一個(gè)內(nèi)存區(qū)域
讓,然后將所有的段都扔進(jìn)去。
MEMORY
{
rom (rx)?: ORIGIN =
0x30000000, LENGTH = 1M
}
注意,我們現(xiàn)在要實(shí)現(xiàn)的是lma,并不是vma,也就是說(shuō)在最后生成的 bin文件中怎么將所有段合在一
起。定義一個(gè)開(kāi)始地址為 0x30000000 ,也就是lma,對(duì)應(yīng)上面的
.text段的lma,長(zhǎng)度自己設(shè),我設(shè)置
為 1M ,其實(shí)溢出會(huì)提示的,隨便設(shè)就OK了。
.text 0x30000000
:
{
*(.text)
*(.rodata)
} AT>rom
.data 0x31000000
:
{
*(.data)
} AT>rom
.bss?:
{
*(.bss)
*(.COMMON)
}
.vector 0x33ffff00
:
{
*(.vector)
}
AT>rom
看到每個(gè)輸出段的末尾都有個(gè) AT>rom
的操作吧?應(yīng)該大概猜到,通俗一點(diǎn)說(shuō)就是:"將這個(gè)輸出段扔到rom
指定的那個(gè)內(nèi)存區(qū)域!!" ,rom是上面已經(jīng)定義了,那么這些操作之后,.text .data .vector
都乖乖的
扔進(jìn) rom
指向的那個(gè)區(qū)域,注意了,我們說(shuō)的是lma,所以不要在意那個(gè)開(kāi)始地址,剛才不是說(shuō)了嗎?那個(gè)
objcopy是從最開(kāi)始的lma開(kāi)始copy而已,這樣出來(lái)的效果和第三點(diǎn)中生成的bin文件其實(shí)是一模一樣的!!
不信的話用UE查看一下 bin 文件的16進(jìn)制代碼,或者查看連接生成的 map文件。這樣做方便很多。
既然 lma 是包含在
elf文件當(dāng)中,那這個(gè)地址到底有什么用?這個(gè)我也不知道了,我猜測(cè),首先,elf文件
是linux下面的可執(zhí)行文件格式,跟windows上面的
.exe文件其實(shí)一樣的,看過(guò)window的可執(zhí)行文件的PE結(jié)構(gòu)
的應(yīng)該知道,真正的代碼前面是有一堆標(biāo)志啊,地址啊,什么的,操作系統(tǒng)就是通過(guò)讀取這部分信息,就知道
應(yīng)該怎么將這個(gè)可執(zhí)行文件加載進(jìn)去。同理,elf文件頭也有一堆有用的信息。不過(guò)對(duì)于我們的嵌入式系統(tǒng)
我估計(jì)應(yīng)該是用不上了(我說(shuō)的是裸奔),基本上都是通過(guò) objcopy 將真正的代碼弄出來(lái)燒些到
flash里面
跑的,所以在嵌入式系統(tǒng)上面,這個(gè) lma我覺(jué)得應(yīng)該是沒(méi)有用處的。
另外,如果用工具調(diào)試的時(shí)候,例如我用的是 openocd,如果加載
elf文件,并不需要指定地址,openocd會(huì)
自動(dòng)的加載,為什么這個(gè)神奇?我覺(jué)得應(yīng)該是elf文件里面包含了 lma 的作用吧,呵呵,其實(shí)挺方便的。
結(jié)束語(yǔ):
被這個(gè)小東西虐待了整整一天半,瘋狂找資料,啃l(wèi)d as等的英文資料手冊(cè),算是實(shí)驗(yàn)了一點(diǎn)成果出來(lái),上面
說(shuō)的技術(shù)對(duì)于我暫時(shí)的應(yīng)用來(lái)說(shuō)已經(jīng)足夠了,也足夠看懂很多例子里面的 ld
script了,網(wǎng)上的資料基本都是
在翻譯 ld 的英文手冊(cè).
總結(jié)
以上是生活随笔為你收集整理的c语言如何输出字母锥子塔,GCC连接脚本学习笔记 zz的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java点赞功能的实现,类似微信点赞,用
- 下一篇: 计算机与通信网络潘书文答案,计算机与通信