日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

c语言如何输出字母锥子塔,GCC连接脚本学习笔记 zz

發(fā)布時(shí)間:2023/12/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c语言如何输出字母锥子塔,GCC连接脚本学习笔记 zz 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

連接腳本將我整整蒙了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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。