在上節(jié)博客中,我們介紹了鏈接器的相關(guān)概念。那么在本節(jié),我們就繼續(xù)來(lái)看看鏈接器,看看它在工程實(shí)踐中的應(yīng)用。我們?cè)诒竟?jié)中做一個(gè)實(shí)驗(yàn),來(lái)模擬在嵌入式中的開(kāi)發(fā)。
成都創(chuàng)新互聯(lián)公司主要從事成都做網(wǎng)站、成都網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)旅順口,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專(zhuān)業(yè),歡迎來(lái)電咨詢(xún)建站服務(wù):13518219792
實(shí)驗(yàn)的目標(biāo):
1、編寫(xiě)一個(gè)“體積受限”的可執(zhí)行程序;
2、通過(guò) makefile 完成代碼編譯;
3、運(yùn)行后在屏幕上打印“D.T.Software”
那么這個(gè)目標(biāo)看似很簡(jiǎn)單,我們?cè)诔鯇W(xué)編程時(shí)就直接打印經(jīng)典的 hello world。其效果類(lèi)似,經(jīng)典的代碼是
#includeint main() { printf("D.T.Software"); return 0; }
當(dāng)然這個(gè)代碼也能運(yùn)行,但是我們的要求是體積受限。什么是體積受限呢?我們都知道,在嵌入式的開(kāi)發(fā)中,內(nèi)存是很少的。因此節(jié)省內(nèi)存成立必不可少的因素,因此我們必須要去掉庫(kù)文件所包含的一些額外的信息,只保留打印語(yǔ)句所需要代碼即可。那么我們來(lái)深度分析下,如何進(jìn)行開(kāi)發(fā)呢?無(wú)疑是直接用匯編代碼進(jìn)行編寫(xiě),然后編寫(xiě)鏈接腳本自定義入口地址了。思路如下
由上可知,我們的解決方案可由以下幾部分完成:
1、通過(guò)內(nèi)嵌匯編自定義打印函數(shù)和退出函數(shù)(INT 80H);
2、通過(guò)鏈接腳本自定義入口函數(shù)(不依賴(lài)于任何庫(kù)和 GCC 內(nèi)置功能);
3、刪除可執(zhí)行程序中的無(wú)用信息(無(wú)用段信息,調(diào)試信息等)。
打印函數(shù)設(shè)計(jì)如下
void print(const char* s, int l) { asm volatile ( "movl $4, %%eax\n" // sysy_write "movl $1, %%ebx\n" "movl %0, %%ecx\n" "movl %1, %%edx\n" "int $0x80 \n" // 80H Service : : "r"(s), "r"(1) // parameter : "eax", "ebx", "ecx", "edx"); }
退出函數(shù)設(shè)計(jì)如下
void exit(int code) { asm volatile ( "movl $1, %%eax\n" // sys_exit "movl %0, %%ebx\n" "int $0x80 \n" // 80H Service : : "r"(code) // parameter : "eax", "ebx"); }
鏈接腳本設(shè)計(jì)如下
ENTRY(program) // 指定入口函數(shù) SECTIONS { .text 0x08048000 + SIZEOF_HEADRS : { *(.text) // 將目標(biāo)文件中的這兩個(gè)段合并進(jìn)入到可執(zhí)行程序中 *(.rodata) } /DISCARD/ : { *(*) // 放棄所有目標(biāo)文件中除 .test 和 .rodata 之外的其他段 } }
最后來(lái)介紹幾個(gè)命令:
1、ld 命令:GNU 的鏈接器,將目標(biāo)文件鏈接為可執(zhí)行程序;GCC 編譯器集中的一員。
2、ld -static:表示 ld 使用靜態(tài)鏈接方式來(lái)產(chǎn)生最終程序,而不是采用默認(rèn)的動(dòng)態(tài)鏈接方式。
3、gcc -fno-builtion:用于關(guān)閉 GCC 內(nèi)置函數(shù)的功能。
在 GCC 中,它提供了很多內(nèi)置函數(shù)(Built-in Function),-fno-builtion 選項(xiàng)會(huì)把一些常用的 C 庫(kù)函數(shù)替換成編譯器的內(nèi)置函數(shù),以達(dá)到優(yōu)化的目的。
program.c 源碼
void print(const char* s, int l); void exit(int code); void program() { print("D.T.Software\n", 13); exit(0); } void print(const char* s, int l) { asm volatile ( "movl $4, %%eax\n" // 調(diào)用系統(tǒng)寫(xiě)函數(shù),編號(hào)為 4 "movl $1, %%ebx\n" // 指定參數(shù),將字符串打印到屏幕 "movl %0, %%ecx\n" // 占位符,代表第一個(gè)參數(shù)(地址) "movl %1, %%edx\n" "int $0x80 \n" // 開(kāi)中斷 : // 輸入?yún)?shù)為空 : "r"(s), "r"(l) // 以只讀形式傳入?yún)?shù) : "eax", "ebx", "ecx", "edx" // 初始化寄存器 ); } void exit(int code) { asm volatile ( "movl $1, %%eax\n" "movl %0, %%ebx\n" "int $0x80 \n" : : "r"(code) : "eax", "ebx" ); }
makefile 編寫(xiě)如下
CC := gcc LD := ld RM := rm -rf TARGET := program.out SRC := $(TARGET:.out=.c) OBJ := $(TARGET:.out=.o) LDS := $(TARGET:.out=.lds) .PHONY : rebuild clean all $(TARGET) : $(OBJ) $(LDS) $(LD) -static -T $(LDS) -o $@ $< @echo "Target File ==> $@" $(OBJ) : $(SRC) $(CC) -fno-builtin -o $@ -c $^ rebuild : clean all all : $(TARGET) clean : $(RM) $(TARGET) $(OBJ)
我們來(lái)運(yùn)行看看結(jié)果如何呢?
結(jié)果正是我們想要的,那么我們這么大費(fèi)周章的來(lái)寫(xiě)這么復(fù)雜的代碼,究竟意義何在呢?我們來(lái)看看它的內(nèi)存大小,以及來(lái)編寫(xiě)一個(gè)正常的 printf 打印語(yǔ)句的代碼,對(duì)比下兩個(gè)可執(zhí)行文件的內(nèi)存大小
我們看到效果是一樣的,但是正常的 printf 打印和我們自定義的鏈接腳本的函數(shù)編寫(xiě)所需的內(nèi)存空間是十多倍的差異。也就是說(shuō),在嵌入式開(kāi)發(fā)中,資源是很寶貴的這種做法是必須的。通過(guò)今天對(duì)鏈接腳本的實(shí)例分析,總結(jié)如下:1、對(duì)于資源受限的嵌入式設(shè)備,需要考慮可執(zhí)行程序的文件大?。?、通過(guò)內(nèi)嵌匯編直接使用系統(tǒng)服務(wù)能夠避開(kāi)相關(guān)庫(kù)的使用;3、可以通過(guò)如下方法控制可執(zhí)行程序的體積大小:a> 最小化庫(kù)的使用(必要情況下自己實(shí)現(xiàn)相關(guān)函數(shù));b> 自定義鏈接腳本,刪除無(wú)用段信息。