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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

連接腳本將我整整蒙了1天零一個上午,做了很多實驗,看了人家不少例子代碼

勉強能駕馭了,讓linker按照我想要的來處理,做個筆記。

1,什么叫輸入段,什么叫輸出段

不知道怎么回事,我對GCC系列的輸入和輸出兩個單詞總是進入思維死角,很簡單

就是 input section 和 output

section,這里不是說翻譯的問題,我覺得是一種

思考的方式的問題。

我的問題就是:既然叫輸入端,那輸入什么?同理,輸出的是什么?不知道其他人

不會不理解這個問題,我自己的話是理解了不少時間了 -v-

所謂的輸出段,是指生成的文件,例如 elf 中的每個段

所謂的輸入段,是指連接的時候提供LD的所有目標文件(OBJ)中的段。

2,lma 和 vma

lma =?load memory

address

vma =?vitual memory

address

如果有研究過ADS的估計有印象,那里有個 RO BASE 和 RW BASE 和 ZI BASE,也

就是說,lma 是裝載地址,vma 是運行地址,想搞清楚這兩個問題,可以閱讀一下

《ARM學習報告(杜云海)》作者寫的很好,將這個問題分析的很透澈。lma 和vma

只是GCC的叫法而已,其實原理是一樣的。

3,兩個基本架構

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",

"elf32-littlearm")

OUTPUT_ARCH(arm)

一句話,照抄......因為我們沒有修改的余地,都是系統默認的關鍵字。第一句

指示系統可以有生成兩種格式,默認是 elf32-arm,端格式是 little endian

4,ENTRY(__ENTRY)

指定入口點,LD的手冊說,ENTRY POINT 就是程序第一條執行的指令,但是,說老

實話,我并不理解,因為這里跟我的理解矛盾了,首先,通常情況,系統需要一個

初始化的 STARTUP.S文件來初始化硬件,也就是 bootloader的第一階段了。那么

很自然,入口點需要設置在這段代碼的第一條指令中,那么正常運行的時候從第一

條指令開始運行。所以這里設置了__ENTRY為入口點,這個在匯編代碼中必須得先

聲明一下為全局,才能用,否則系統找不到。例如:

.global __ENTRY

但是問題是,如果我用同樣的辦法,設置另外一個不是第一條指令的入口點,LD并

沒有報錯,但是問題來了,生成的文件和剛才設置入口點為 __ENTRY 的時候一模一

樣,這就蒙了,到底這個入口點是怎么回事?

記得以前ADS的時候也碰到過 entry point的問題,下載仿真的時候確實是自動跳轉

到 entry point中運行。

我想到的可能的原因,第一,生成 elf 文件并不是能直接用在嵌入式平臺上面裸跑

的,因為我們并沒有操作系統,我們不需要elf文件頭的那些指示信息提供給操作

系統,指示系統怎么去加載文件,在嵌入式上面的完全沒有那個必要,只需要將實

際的代碼提取出來,直接運行就OK,也就是 objcopy的操作,所以我覺得,在裸奔

的嵌入式系統上面,entry point是沒有意義的,只需要指向整個代碼最開始的指

令就OK了。

暫時我還是不能清晰的理解這個東西。先放下。以后碰到問題再分析。

5,一個輸出段的標準格式

section [address] [(type)] : [AT(lma)]

{

output-section-command

output-section-command

...

} [>region]

[AT>lma_region] [:phdr :phdr ...]

[=fillexp]

前面也說了,所謂的輸出段是指最終生成的文件里面的段,所以一個輸出段就可以

理解為最終文件里面的一個塊,那么多個塊合起來就是一個完成文件了。

而每個小塊又分別有什么文件來組成呢?那就是輸入段了。

我自己實際用到有下面的一些,其他暫時不會用。

section_name?vma :

AT(lma)

{

output-section-command

output-section-command

...

}

[AT>lma_region]

section_name 根據ld手冊說是有個確定的名字,其他沒啥,自己添加一些新段也是

可以的。

默認的4個段是必須有的

.text 代碼

.rodata 常量,例如字符串什么的

.data 初始化的全局變量

.bss?沒有初始化的全局變量

其實沒什么,可以說,都是固定的,所以一句話,照抄。

段名字后面緊跟的是 vma ,也就是這個段在程序運行的時候的地址,例如

.text 0x30000000 :

{

*(.text)

}

表示的是代碼的運行時地址為 0x30000000 假如你的ROM在 0x0 地址,程序放在ROM

中,那個時候程序是不能正常運行的(位置無關代碼除外),必須將代碼COPY到VMA

也就是 0x30000000 中才能正常運行。

至于那個 AT(lma) 的關鍵字,只指示代碼連接的時候應該放在什么地方,注意好

這個英文是 load memory address,是指程序應該裝載在什么地方,而不是指這個段

應該在最后生成的bin文件的位置!!!這個東西蒙騙了我,讓我郁悶了1天。elf格

式的文件里面不但包含了代碼,還包含了各種各樣的信息,例如上面說的每個段的

lma 和vma,還有其他信息都包含在里面了。

默認狀態下,lma 是等于當前的vma的,例如

.text 0x30000000

:

{

*(.text)

*(.rodata)

}

.data 0x33ffff00

:

{

*(.data)

}

例如我們基本的兩個段,我們指定了.text 和.data段的vma,但是沒有指定lma,那么

lma到底應該是多少?很簡單,ld認為當前的lma和vma是相同的,所以lma應該分別是

0x30000000

0x33ffff00,編譯生成的elf文件很小,但是objcopy出來的文件卻非常

巨大,達到了60多MB,這是什么問題?

elf文件很聰明,他只是保存了信息,.text段的lma是0x30000000,那么elf就保存了

知道本程序的代碼應該加載到 0x30000000,然后又保存了.data的lma,我們留意到

中間有很多的地址空間是空的,并沒有實際的代碼,elf怎么處理?elf只保存了兩個

地址和實際的代碼,而對于其他空間里面的代碼他并不處理,所以可以看出,最后生成

的elf文件并不大,也就100多KB而已,但是后來的OBJCOPY操作中,從elf文件中copy

出程序代碼,這下就糟了,objcopy是從最開始的lma開始,這里是 0x30000000一直

復制到最尾段的lma,這里是 0x33ffff00,中間沒有代碼地方全部補零,那么60多MB

的大bin文件就是這樣來的。

可以驗證一下,如果手動指定開始的lma為0的話

.text 0x30000000 :

AT(0)

{

*(.text)

*(.rodata)

}

.data 0x33ffff00

:

{

*(.data)

}

其中.text段的lma被AT強制指定為0,那么objcop出來的bin文件相當的華麗,達到了

700多MB,為什么?都說了,從0開始到 0x33ffff00,中間補零,字節數相當的可觀呢。

一般我們常用的做法是:

1,.data段的 lma 和 vma 都是緊跟著 .text 的,或者用ARM的說法就是

RW段緊跟著

RO段,這樣的做法非常簡單

.text 0x30000000

:

{

*(.text)

*(.rodata)

}

.data

:

{

*(.data)

}

.bss?:

{

*(.bss)

*(.COMMON)

}

只指定RO BASE,然后所有代碼都是跟著RO BASE分配,這樣非常簡單。

2,.data段分離出來,連接到不同的vma運行時地址。

.text 0x30000000

:

{

*(.text)

*(.rodata)

}

.data 0x31000000 :

AT(LOADADDR(.text) +

SIZEOF(.text))

{

*(.data)

}

.bss?:

{

*(.bss)

*(.COMMON)

}

其實也不難解決,像上面的代碼那樣做就OK了,上面也分析了,如果vma不同的話,objcopy

會一直復制,這樣生成的bin文件會很大,怎么解決?很簡單,手工指定 .data段的lma地址

讓 .data段的 lma 緊緊跟著 .text段的末尾,這樣生成的 bin

文件就會很漂亮,跟第一種

辦法生成的bin文件結構一模一樣!!

AT(LOADADDR(.text) +

SIZEOF(.text))

這個指令大概解釋一下,AT 是指定lma 的,然后里面用了兩個指令 LOADADDR ,和名字一樣

這個指令是用來求 lma 地址的! SIZEOF 也就是名字那樣,求大小的

LOADADDR(.text) 求出 .text 段的 lma,注意是開始地址

SIZEOF(.text)?求出 .text

段的大小

AT(LOADADDR(.text) +

SIZEOF(.text))?的效果就是,指定

.data段的lma在 .text段lma

的結尾處!

這里補充一下,還有一個指令 ADDR(.text) 這個是求vma的,不是求lma。

另外,注意一下 .bss段的lma和 .data段的 lma是一樣的,這也反映了一個實質問題 .bss

只分配運行地址 vma,并不實際占空間的。

3,如果我想自己添加一些段,應該怎么去實現?

例如我要添加一個 .vector 的段,里面放的是一些數據,怎么實現?

(1)如果在匯編代碼里面添加,那么可以新啟動一個段

例如在 2440init.S 中添加 .vector 段

.section .text

....

....

(其他代碼)

.section?.vector?@

在這里聲明一個段,并且放連個數據

.word?0x55

.word?0xaa

匯編代碼段的開始由?.section

聲明,接著后面的都屬于這個段,直到第二個 .section 聲明

為止。

我這個 .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)

}

可以看出,形式其實是一樣,不過看一下,添加的段的lma放在 .data 段的lma的后面,前面也說

看 .bss 和 .data的lma是一樣的,所以其實無視掉 .bss段就OK了。

(2)在C語言中怎么添加一個變量指定放到 .vector段

很簡單,用GNU擴展語法(注意了,是GNU系列工具通用而已,例如gcc,這個并不是C的標準)

格式如下

unsigned int __attribute__((section(".vector")))

vec=0x9988;

定義一個 vec 變量,值為 0x9988,分配在 .vector 段,編譯后用 objdump

一下查看匯編代碼

可以發現到

Disassembly of section .vector:

33ffff00 :

33ffff00:?00009988 ?.word?0x00009988

33ffff04:?00000055 ?.word?0x00000055

33ffff08:?000000aa ?.word?0x000000aa

看到沒有?剛才說的在匯編代碼和C代碼里面定義的數值都被連接進去了 .vector段了,vma也正確

最后還可以看看生成的 bin 文件,看看最后的幾個數據是不是就是 0x9988 0x55 0xaa

?這樣應該

就理解了整個連接的過程了吧?

4,MEMORY 命令在指定lma中的使用

每個段都要用 AT 來指定具體的位置,其實挺煩的,我們有更加簡單的辦法,我們定義一個內存區域

讓,然后將所有的段都扔進去。

MEMORY

{

rom (rx)?: ORIGIN =

0x30000000, LENGTH = 1M

}

注意,我們現在要實現的是lma,并不是vma,也就是說在最后生成的 bin文件中怎么將所有段合在一

起。定義一個開始地址為 0x30000000 ,也就是lma,對應上面的

.text段的lma,長度自己設,我設置

為 1M ,其實溢出會提示的,隨便設就OK了。

.text 0x30000000

:

{

*(.text)

*(.rodata)

} AT>rom

.data 0x31000000

:

{

*(.data)

} AT>rom

.bss?:

{

*(.bss)

*(.COMMON)

}

.vector 0x33ffff00

:

{

*(.vector)

}

AT>rom

看到每個輸出段的末尾都有個 AT>rom

的操作吧?應該大概猜到,通俗一點說就是:"將這個輸出段扔到rom

指定的那個內存區域!!" ,rom是上面已經定義了,那么這些操作之后,.text .data .vector

都乖乖的

扔進 rom

指向的那個區域,注意了,我們說的是lma,所以不要在意那個開始地址,剛才不是說了嗎?那個

objcopy是從最開始的lma開始copy而已,這樣出來的效果和第三點中生成的bin文件其實是一模一樣的!!

不信的話用UE查看一下 bin 文件的16進制代碼,或者查看連接生成的 map文件。這樣做方便很多。

既然 lma 是包含在

elf文件當中,那這個地址到底有什么用?這個我也不知道了,我猜測,首先,elf文件

是linux下面的可執行文件格式,跟windows上面的

.exe文件其實一樣的,看過window的可執行文件的PE結構

的應該知道,真正的代碼前面是有一堆標志啊,地址啊,什么的,操作系統就是通過讀取這部分信息,就知道

應該怎么將這個可執行文件加載進去。同理,elf文件頭也有一堆有用的信息。不過對于我們的嵌入式系統

我估計應該是用不上了(我說的是裸奔),基本上都是通過 objcopy 將真正的代碼弄出來燒些到

flash里面

跑的,所以在嵌入式系統上面,這個 lma我覺得應該是沒有用處的。

另外,如果用工具調試的時候,例如我用的是 openocd,如果加載

elf文件,并不需要指定地址,openocd會

自動的加載,為什么這個神奇?我覺得應該是elf文件里面包含了 lma 的作用吧,呵呵,其實挺方便的。

結束語:

被這個小東西虐待了整整一天半,瘋狂找資料,啃ld as等的英文資料手冊,算是實驗了一點成果出來,上面

說的技術對于我暫時的應用來說已經足夠了,也足夠看懂很多例子里面的 ld

script了,網上的資料基本都是

在翻譯 ld 的英文手冊.

總結

以上是生活随笔為你收集整理的c语言如何输出字母锥子塔,GCC连接脚本学习笔记 zz的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。