0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

linker script的原理及使用技巧

嵌入式IoT ? 來源:CSDN博客 ? 作者:CSDN博客 ? 2020-08-31 14:58 ? 次閱讀

1.概述

編譯器將編寫的C程序代碼進(jìn)行翻譯,變成機(jī)器可以執(zhí)行的程序,這個(gè)大致上可以分為四個(gè)步驟:預(yù)編譯、編譯、匯編、鏈接。

其中編譯和鏈接這兩個(gè)過程比較重要。編譯過程就是將源代碼通過程序翻譯后生成機(jī)器可以認(rèn)識(shí)的機(jī)器語言。而鏈接就是將目標(biāo)文件進(jìn)行組合,最后生成在特定平臺(tái)上可以正常運(yùn)行的可執(zhí)行程序。

本文主要描述鏈接這個(gè)過程。由于匯編器生成的目標(biāo)代碼(.o)文件不能被立即執(zhí)行,因?yàn)槔锩嬉话愣紩?huì)包含其他的源文件中的符號(hào)、變量或者函數(shù)調(diào)用等等,要想處理好這些問題,就必須將程序進(jìn)行鏈接。

2.靜態(tài)鏈接和動(dòng)態(tài)鏈接

根據(jù)開發(fā)人員指定庫函數(shù)的鏈接方式,鏈接又分為動(dòng)態(tài)鏈接和靜態(tài)鏈接兩種。

2.1 靜態(tài)鏈接

我們?cè)谶M(jìn)行嵌入式開發(fā)過程中時(shí),往往接觸到比較多的就是靜態(tài)鏈接。前面說過,編譯器將源代碼編譯成一個(gè)一個(gè)的.o文件的目標(biāo)文件,這些文件又會(huì)存在各種依賴關(guān)系,所以將各種.o文件匯集到一起。

這種方式編譯出來的程序,可以直接運(yùn)行,不依賴于外部庫文件。

2.2 動(dòng)態(tài)鏈接

當(dāng)涉及到程序比較多的時(shí)候,如果每個(gè)程序都依賴于同樣的一個(gè)庫里面的函數(shù),那么這個(gè)庫就是共享的。

2.3 兩種鏈接方式的對(duì)比

靜態(tài)鏈接方式,適合單應(yīng)用程序,比如嵌入式rtos等等。這種將所有的目標(biāo)文件都鏈接到一個(gè)可執(zhí)行的文件中,所以執(zhí)行效率很高。但是文件內(nèi)存占用大。動(dòng)態(tài)鏈接時(shí),如果app1運(yùn)行將libc加載到內(nèi)存中,下次app2直接可以從內(nèi)存中使用。這種方式可以讓每個(gè)程序的文件大小比較小,但是相對(duì)于的,執(zhí)行效率相對(duì)比較低。

3.鏈接腳本

一般在進(jìn)行g(shù)cc進(jìn)行鏈接的時(shí)候,都會(huì)考慮到鏈接腳本(linker script),該文件一般以lds文件作為后綴名。該文件規(guī)定了將特定的section放到文件內(nèi),并且控制著輸出文件的布局。一般來說,自己編寫的鏈接腳本可以指定傳遞參數(shù)-T xxx.lds,其中xxx.lds則是自己編寫的鏈接腳本。

xxx.lds基本格式如下:

SECTIONS{sections-commandsections-command......}

那么什么是sections?每個(gè)目標(biāo)文件都有一些列的段,比如代碼段、數(shù)據(jù)段、bss段等等。

3.1 鏈接腳本實(shí)例分析

如果沒有實(shí)際的東西,那么說起理論來將索然無味。下面就具體來看下面的一個(gè)鏈接腳本的布局。

一個(gè)最簡(jiǎn)單的linker腳本文件如下:

SECTIONS{.=0x10000;/*(1)*/.text:{*(.text)}/*(2)*/.=0x800000;/*(3)*/.data:{*(.data)}/*(4)*/.bss:{*(.bss)}/*(5)*/}

下面來解釋一下上述的程序

(1).的定義是location counter,也就是把當(dāng)前的程序指向0x10000,如果沒有這個(gè)地址,默認(rèn)該符號(hào)的值為0?;蛘咴趃cc的鏈接選項(xiàng)中-Ttext 0x10000也是一樣的效果。

(2).text指向代碼段,其中*這個(gè)符號(hào)代表所有的輸入文件的.text section合并成的一個(gè)

(3).=0x800000將定位器的符號(hào)設(shè)置成0x800000

(4).data指向所有輸入文件的數(shù)據(jù)段,并且這個(gè)地址的起始為0x800000

(5).bss表示所有輸入文件的bss段

上述從一個(gè)最簡(jiǎn)單的鏈接腳本分析了鏈接腳本的語法格式。

3.2 內(nèi)存的分段鏈接

如果一塊內(nèi)存在sram中,一塊內(nèi)存在sdram中,這兩塊地址并不連續(xù),那么需求是將代碼段(.text)段放在sram區(qū),數(shù)據(jù)段(.data)與bss段放在ddr區(qū),這時(shí)鏈接腳本該如何進(jìn)行設(shè)計(jì)。

首先假設(shè)sram的空間地址為0x1000處開始的,可用空間為1M。ddr的地址空間為0x40000000,目前只用到2M。

首先可用在lds文件中做一個(gè)聲明

MEMORY{ram : org = 0x00001000, len = 1Mddr : org = 0x40000000, len = 2M}

然后鏈接腳本可用以如下的方式進(jìn)行編寫

SECTIONS{ . = 0x00001000; . = ALIGN(4096); .text:{*(.text)}>ram.data:{*(.data)}>ddr.bss:{*(.bss)}>ddr}

只需要指定對(duì)應(yīng)的鏈接段即可。

3.3 指定第一個(gè)文件的鏈接

有的時(shí)候,需要考慮到鏈接順序的問題,比如在有些處理器中,系統(tǒng)從一個(gè)固定的地址啟動(dòng),但這個(gè)地址一定最開始的時(shí)候會(huì)存放一個(gè)異常向量表。從異常向量表中跳轉(zhuǎn)到實(shí)際的入口函數(shù)處去執(zhí)行。那么這該如何進(jìn)行設(shè)計(jì)?

一般來說我們鏈接代碼段的時(shí)候,都是鏈接的.text section。但是,我們也可用指定該文件的代碼段。比如可以在第一個(gè)需要編譯的文件頭部加上

.section ".text.entrypoint"

這樣就會(huì)指定

SECTIONS{ . = 0x00001000; . = ALIGN(4096); .text: { KEEP(*(.text.entrypoint)) *(.text) }>ram.data:{*(.data)}>ddr.bss:{*(.bss)}>ddr}

其中keep相當(dāng)于告訴編譯器,這部分不要被垃圾回收。

3.4 自己定義代碼段名字

有些時(shí)候,需要將特定的符號(hào)指定到特定的地址,這樣的好處就是可用通過地址訪問對(duì)應(yīng)的函數(shù)。這個(gè)應(yīng)用在rt-thread rtos操作系統(tǒng)應(yīng)用的比較經(jīng)典。

在很多時(shí)候,需要指定初始化的執(zhí)行順序。比如驅(qū)動(dòng)的初始化順序等等。實(shí)現(xiàn)這種功能有很多種實(shí)現(xiàn)方式,上中下策都可以,下策就是直接通過函數(shù)調(diào)用關(guān)系進(jìn)行調(diào)用。中策就是采用回調(diào)函數(shù)的方式進(jìn)行設(shè)計(jì)。上策就是利用linker script進(jìn)行函數(shù)擴(kuò)展。

直接調(diào)用的方式實(shí)現(xiàn)起來比較簡(jiǎn)單,也比較好理解,直接調(diào)用對(duì)應(yīng)的函數(shù)即可。

回調(diào)函數(shù)就是利用函數(shù)指針,當(dāng)回調(diào)函數(shù)綁定了指針時(shí),執(zhí)行該回調(diào)函數(shù)檢查該函數(shù)是否綁定,然后選擇執(zhí)行。這樣可用降低耦合性。

采用linker script方式時(shí),相當(dāng)于把函數(shù)的指針集合到一個(gè).text的空間中。這樣執(zhí)行的時(shí)候,只需要找到linker中對(duì)應(yīng)的地址,轉(zhuǎn)換成函數(shù)即可,這種方式就很好擴(kuò)展。

在rt-thread中,函數(shù)導(dǎo)出命令使用了這種技巧

/* board init routines will be called in board_init() function */#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* pre/device/component/env/app init routines will be called in init_thread *//* components pre-initialization (pure software initilization) */#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")/* device initialization */#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")/* components initialization (dfs, lwip, ...) */#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")/* environment initialization (mount disk, ...) */#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")/* appliation initialization (rtgui application etc ...) */#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")

而INIT_EXPORT的實(shí)現(xiàn)如下:

#define INIT_EXPORT(fn, level) RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn

而在鏈接腳本中編寫如下:

. = ALIGN(4);__rt_init_start = .;KEEP(*(SORT(.rti_fn*)))__rt_init_end = .;. = ALIGN(4);

最后可用查看map文件,查看地址

*(SORT(.rti_fn*)) .rti_fn.0 0xffffffff802bd418 0x8 buildkernelsrccomponents.o 0xffffffff802bd418 __rt_init_rti_start .rti_fn.0.end 0xffffffff802bd420 0x8 buildkernelsrccomponents.o 0xffffffff802bd420 __rt_init_rti_board_start .rti_fn.1 0xffffffff802bd428 0x8 builddriversdrv_gpio.o 0xffffffff802bd428 __rt_init_loongson_pin_init .rti_fn.1.end 0xffffffff802bd430 0x8 buildkernelsrccomponents.o 0xffffffff802bd430 __rt_init_rti_board_end .rti_fn.2 0xffffffff802bd438 0x8 buildkernelcomponentsdfssrcdfs.o 0xffffffff802bd438 __rt_init_dfs_init .rti_fn.2 0xffffffff802bd440 0x8 buildkernelcomponents etlwip-2.0.2srcarchsys_arch.o 0xffffffff802bd440 __rt_init_lwip_system_init .rti_fn.3 0xffffffff802bd448 0x8 builddriversdrv_rtc.o 0xffffffff802bd448 __rt_init_rt_hw_rtc_init .rti_fn.3 0xffffffff802bd450 0x8 buildkernelcomponentsdriverssrcworkqueue.o 0xffffffff802bd450 __rt_init_rt_work_sys_workqueue_init .rti_fn.4 0xffffffff802bd458 0x8 builddrivers etsynopGMAC.o 0xffffffff802bd458 __rt_init_rt_hw_eth_init .rti_fn.4 0xffffffff802bd460 0x8 buildkernelcomponentsdfsfilesystemselmfatdfs_elm.o 0xffffffff802bd460 __rt_init_elm_init .rti_fn.4 0xffffffff802bd468 0x8 buildkernelcomponentslibccompilers ewliblibc.o 0xffffffff802bd468 __rt_init_libc_system_init .rti_fn.4 0xffffffff802bd470 0x8 buildkernelcomponents etsal_socketsrcsal_socket.o 0xffffffff802bd470 __rt_init_sal_init .rti_fn.6 0xffffffff802bd478 0x8 buildkernelcomponentsfinshshell.o 0xffffffff802bd478 __rt_init_finsh_system_init .rti_fn.6.end 0xffffffff802bd480 0x8 buildkernelsrccomponents.o 0xffffffff802bd480 __rt_init_rti_end 0xffffffff802bd488 __rt_init_end = . 0xffffffff802bd488 . = ALIGN (0x4) 0xffffffff802bd488 . = ALIGN (0x4)

實(shí)際上在執(zhí)行的時(shí)候,實(shí)現(xiàn)如下

/** * RT-Thread Components Initialization for board */void rt_components_board_init(void){#if RT_DEBUG_INIT int result; const struct rt_init_desc *desc; for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++) { rt_kprintf("initialize %s", desc->fn_name); result = desc->fn(); rt_kprintf(":%d done ", result); }#else volatile const init_fn_t *fn_ptr; for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++) { (*fn_ptr)(); }#endif}

并不是訪問的具體的函數(shù),而是從__rt_init_rti_board_start指向的指針開始,不停向下執(zhí)行,直到__rt_init_rti_board_end結(jié)尾。這樣就不依賴于具體的函數(shù)的實(shí)現(xiàn)了。所以函數(shù)的擴(kuò)展性非常好。

4.總結(jié)

以上介紹了linker script的原理,以及在實(shí)際使用過程中的幾個(gè)使用的技巧。這些都是在實(shí)際的項(xiàng)目中總結(jié)的來的,其實(shí)理解了linker script將可用完成很多有趣的使用技巧。只是平時(shí)我們并沒有特別關(guān)注這個(gè)文件的使用,也并沒有實(shí)際去編寫一個(gè)linker script完成一個(gè)工程的構(gòu)建。關(guān)于linker script的語法和使用,還有很多可以自由發(fā)揮的地方。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Linker
    +關(guān)注

    關(guān)注

    0

    文章

    3

    瀏覽量

    1641

原文標(biāo)題:鏈接腳本linker script的妙用

文章出處:【微信號(hào):Embeded_IoT,微信公眾號(hào):嵌入式IoT】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    求助,關(guān)于PSOC4模擬EEPROM的checksum問題求解

    咨詢個(gè)問題,當(dāng)我們用PSOC4的Em_EEPROM組件時(shí),工程生成的Hex1文件的checksum是不包含了EEPROM的部分吧(通過Custom Linker Script調(diào)用cm0gcc.ld
    發(fā)表于 02-02 08:48

    Eclipse如何設(shè)置Linker文件路徑?

    電腦沒有F盤,可Eclispe 編譯提示找不到F盤的linker script file,暈了,找半天沒找到,哪里可以設(shè)置linker路徑呢?
    發(fā)表于 02-22 07:31

    esp-idf-tools-setup-offline-2.11.exe編譯提示Could NOT find Perl,這個(gè)Perl如何安裝?

    linker script D:/esp32/esp-idf/components/esp_rom/esp32s2/ld/esp32s2.rom.ld -- Adding linker sc
    發(fā)表于 06-20 07:24

    ESP32-S2使用IDF MASTER分支編譯出現(xiàn)/http-parser文件夾缺失怎么解決?

    \") -- App \"app_main\" version: a335f49-dirty -- Adding linker script /mnt/g/project
    發(fā)表于 06-21 09:32

    idf.py build報(bào)錯(cuò)ninja failed with exit code 1如何解決?

    that the project does not need compatibility with older versions. -- Adding linker script /Users/yuyuan/esp
    發(fā)表于 06-24 06:20

    esp32s2添加IOT組件的配置出錯(cuò)了怎么解決?

    script C:/Users/xuyux/Desktop/esp-idf/components/esp_rom/esp32s2/ld/esp32s2.rom.ld-- Adding linker
    發(fā)表于 06-28 07:39

    找不到鏈接器腳本PIC32MX570F512L

    在Microchip提供的AN1388源代碼中沒有PIC32 MX570F512L的鏈接器腳本? 以上來自于百度翻譯 以下為原文 hellowhy there isn't any linker
    發(fā)表于 09-11 14:59

    Harmony V2.05 Bootloader鏈接器腳本不起作用

    更容易,但我想不會(huì)。 以上來自于百度翻譯 以下為原文 Harmony V2.05.01MHCV2.05.02MplabX V4.05PIC32MX530F128H The Linker script
    發(fā)表于 11-14 16:21

    Bootloader錯(cuò)誤:警告:鏈接描述文件未指定CRT0_STARTUP

    script error ...Warning: linker script did not specify CRT0_STARTUPThe error is because the lin
    發(fā)表于 04-23 12:28

    IDF-4.3.1編譯提示Could NOT find Perl,這個(gè)Perl如何安裝?

    : PERL_EXECUTABLE)-- App "smart-panel" version: v4.3.1-- Adding linker script D:/esp32
    發(fā)表于 02-20 06:22

    ESP-AT編譯失敗的原因?如何解決?

    : PERL_EXECUTABLE)-- Adding linker script D:/myWork/esp/esp-at/test-main/build/esp-idf/esp32/esp32_out.ld--
    發(fā)表于 03-08 08:22

    ESP32S2添加IOT組件的配置出錯(cuò)的原因?如何解決?

    ;lvgl_example" version: 1-- Adding linker script C:/Users/xuyux/Desktop/esp-idf/components/esp_rom
    發(fā)表于 03-09 08:38

    在lpuart_edma_rb_transfer SDK示例中使用BOARD_SDRAM報(bào)錯(cuò)怎么解決?

    > Settings > Tools Settings > MCU Linker > Managed Linker Script 中我將每個(gè)區(qū)域(堆、堆棧、.data
    發(fā)表于 03-15 07:01

    ESP-AT編譯失敗的原因?如何解決?

    linker script D:/myWork/esp/esp-at/test-main/build/esp-idf/esp32/esp32_out.ld -- Adding linker
    發(fā)表于 04-24 06:32

    D語言編寫單片(STM32F401cc)機(jī)應(yīng)用需要用到的技巧 - 主入口函數(shù)

    D語言編寫單片機(jī)應(yīng)用需要用到的技巧 - 主入口函數(shù)入口函數(shù)入口函數(shù)單片機(jī)一定會(huì)需要一個(gè)主入口函數(shù),至于是那個(gè)主要看linker script中的定義,并不一定是你看到的main函數(shù)(多數(shù)情況下也
    發(fā)表于 11-29 21:06 ?13次下載
    D語言編寫單片(STM32F401cc)機(jī)應(yīng)用需要用到的技巧 - 主入口函數(shù)