加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

從文件角度,了解Cortex-M開發(fā)(2)

2020/08/21
187
閱讀需 24 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

在前一節(jié)課《源文件(.c/.h/.s)》里,痞子衡給大家系統(tǒng)地介紹了 source 文件,source 文件是嵌入式工程里典型的 input 文件,那么還有沒有其他類型的 input 文件?既然痞子衡這么提問了,那答案肯定是有啦。今天痞子衡要講的 linker 文件就屬于另一種 input 文件。

linker 文件顧名思義就是嵌入式工程在鏈接階段所要用到的文件,source 文件在編譯過程完成之后(此時已經(jīng)是機器可識別的二進制機器碼數(shù)據(jù)),需要再經(jīng)過鏈接器從而將二進制數(shù)據(jù)有序組織起來形成最終的二進制可執(zhí)行文件,該二進制文件最終會被下載進芯片內(nèi)部非易失性存儲器里。linker 文件就是用來指示鏈接器如何組織編譯生成的二進制數(shù)據(jù)。

linker 文件是跟 IDE 息息相關(guān)的,本文以 IAR EWARM 為例介紹 linker 文件,其他 IDE 下的 linker 文件可觸類旁通。

一、 嵌入式系統(tǒng)中的 section

在講 linker 文件之前,痞子衡必須先跟大家理清一個嵌入式系統(tǒng)中很重要的概念 -section。那么什么是 section?我們寫的 C 或者匯編 source 文件里都是各種應(yīng)用代碼,這些代碼按功能可以分為很多種類,比如常量、變量、函數(shù)、堆棧等,而相同類型的代碼的集合便是一個 section,鏈接器在鏈接時組織數(shù)據(jù)的基本單元便是 section。那么一個典型的嵌入式系統(tǒng)中到底有多少種 section 呢?下面列出了 IAR 里默認的所有 section,那些常見 section 在后續(xù)介紹 linker 文件里會被提到。

// 常見 Section

.bss                 // Holds zero-initialized static and global variables.

CSTACK               // Holds the stack used by C or C++ programs.

.data                // Holds static and global initialized variables.

.data_init           // Holds initial values for .data sections when the linker directive initialize is used.

HEAP                 // Holds the heap used for dynamically allocated data.

.intvec              // Holds the reset vector table

.noinit              // Holds __no_init static and global variables.

.rodata              // Holds constant data.

.text                // Holds the program code.

.textrw              // Holds __ramfunc declared program code.

.textrw_init         // Holds initializers for the .textrw declared section.

// 較冷僻 Section

.exc.text            // Holds exception-related code.

__iar_tls.$$DATA     // Holds initial values for TLS variables.

.iar.dynexit         // Holds the atexit table.

.init_array          // Holds a table of dynamic initialization functions.

IRQ_STACK            // Holds the stack for interrupt requests, IRQ, and exceptions.

.preinit_array       // Holds a table of dynamic initialization functions.

.prepreinit_array    // Holds a table of dynamic initialization functions.

Veneer$$CMSE         // Holds secure gateway veneers.

// 更冷僻 Section

.debug               // Contains debug information in the DWARF format

.iar.debug           // Contains supplemental debug information in an IAR format

.comment             // Contains the tools and command lines used for building the file

.rel or .rela        // Contains ELF relocation information

.symtab              // Contains the symbol table for a file

.strtab              // Contains the names of the symbol in the symbol table

.shstrtab            // Contains the names of the sections.

Note:上述 section 的詳細解釋請查閱 IAR 軟件安裝目錄下 IAR SystemsEmbedded Workbench

xxxarmdocEWARM_DevelopmentGuide.ENU.pdf 文檔里的 Section reference 一節(jié)。

二、解析 linker 文件

知道了 section 概念,那便可開始深入了解 linker 文件,什么是 linker 文件?linker 文件是按 IDE 規(guī)定的語法寫成的用于指示鏈接器分配各 section 在嵌入式系統(tǒng)存儲器中存放位置的文件。大家都知道嵌入式系統(tǒng)存儲器主要分為兩類:ROM(非易失性),RAM(易失性),所以相應(yīng)的這些 section 根據(jù)存放的存儲器位置不同也分為兩類屬性:readonly, readwrite。實際上 linker 文件的工作就是將 readonly section 放進 ROM,readwrite section 放進 RAM。

那么到底該如何編寫工程的 linker 文件呢?正如前面所言,linker 文件也是有語法的,而且這語法是由 IDE 指定的,所以必須要先掌握 IDE 制定的語法規(guī)則,linker 文件語法規(guī)則相對簡單,最常用的關(guān)鍵字就是如下 8 個:

// 動詞類關(guān)鍵字

define                // 定義各種空間范圍、長度

initialize            // 設(shè)置 section 初始化方法

place in              // 放置 section 于某 region 中(具體地址由鏈接器分配)

place at              // 放置 section 于某絕對地址處

// 名詞類關(guān)鍵字

symbol                // 各種空間范圍、長度的標(biāo)識

memory                // 整個 ARM 內(nèi)存空間的標(biāo)識

region                // 在整個 ARM 內(nèi)存空間中劃分某 region 空間的標(biāo)識

block                 // 多個 section 的集合塊的標(biāo)識

Note:上述 linker 語法的詳細解釋請查閱 IAR 軟件安裝目錄下 IAR SystemsEmbedded Workbench

xxxarmdocEWARM_DevelopmentGuide.ENU.pdf 文檔里的 The linker configuration file 一節(jié)。

到這里我們已經(jīng)可以開始愉快地寫 linker 文件了,是不是有點按捺不住了?來吧,只需要三步走,Let's do it。

此處假設(shè) MCU 物理空間為:ROM(0x0 - 0x1ffff)、RAM(0x10000000 - 0x1000ffff),痞子衡要寫的 linker 要求如下:

中斷向量表必須放置于 ROM 起始地址 0x0,且必須 256 字節(jié)對齊

STACK 大小為 8KB,HEAP 大小為 1KB,且必須 8 字節(jié)對齊

SATCK 必須放置在 RAM 起始地址 0x10000000

其余 section 放置在正確的 region 里,具體空間由鏈接器自動分配

2.1 定義物理空間

第一步我們先定義 3 塊互不重疊的空間 ROM_region、RAM_region、STACK_region,其中 ROM_region 對應(yīng)的是真實的 ROM 空間,RAM_region 和 STACK_region 組合成真實的 RAM 空間。

// 定義物理空間邊界

define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;

define symbol __ICFEDIT_region_ROM_end__   = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);

define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;

define symbol __ICFEDIT_region_RAM_end__   = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);

define symbol __ICFEDIT_intvec_start__     = __ICFEDIT_region_ROM_start__;

// 定義堆棧長度

define symbol __ICFEDIT_size_cstack__      = (8*1024);

define symbol __ICFEDIT_size_heap__        = (1*1024);

// 定義各 region 具體空間范圍

define memory mem with size = 4G;

define region ROM_region    = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];

define region STACK_region  = mem:[from __ICFEDIT_region_RAM_start__ to  __ICFEDIT_region_RAM_start__ +

__ICFEDIT_size_cstack__ - 1];

define region RAM_region    = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__  to

__ICFEDIT_region_RAM_end__];

2.2 定義 section 集合

第二步是自定義 section 集合塊,細心的朋友可以看到右邊花括號里包含的都是上一節(jié)介紹的系統(tǒng)默認 section,我們會把具有相同屬性的 section 集合成到一個 block 里,方便下一步的放置工作。

// 定義堆棧塊及其屬性

define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };

define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };

// 定義 section 集合塊

define block Vectors with alignment=256 { readonly section .intvec };

define block CodeRelocate               { section .textrw_init };

define block CodeRelocateRam            { section .textrw };

define block ApplicationFlash           { readonly, block CodeRelocate };

define block ApplicationRam             { readwrite, block CodeRelocateRam, block HEAP };

有朋友可能會疑問,為何要定義 CodeRelocate、CodeRelocateRam 這兩個 block?按道理說這兩個 block 對應(yīng)的 section 可以分別放進 ApplicationFlash 和 ApplicationRam,那為何多此一舉?仔細上過痞子衡前一節(jié)課 source 文件的朋友肯定就知道答案了,在那節(jié)課里介紹的 startup.c 文件里有一個叫 init_data_bss()的函數(shù),這個函數(shù)會完成初始化 CodeRelocateRam 塊的功能,它找尋的就是 CodeRelocate 段名字,這個名字比系統(tǒng)默認的 textrw 名字看起來更清晰易懂。

2.3 安置 section 集合

第三步便是處理放置那些 section 集合塊了,在放置集合塊之前還有 initialize manually 語句,為什么會有這些語句?還是得結(jié)合前面提及的 startup.c 文件里的 init_data_bss()函數(shù)來說,這個函數(shù)是開發(fā)者自己實現(xiàn)的 data,bss 段的初始化,所以此處需要通知 IDE,你不需要再幫我做初始化工作了。

// 設(shè)置初始化方法

initialize manually { readwrite };

initialize manually { section .data};

initialize manually { section .textrw };

do not initialize   { section .noinit };

// 放置 section 集合塊

place at start of ROM_region { block Vectors };

//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };

place in ROM_region          { block ApplicationFlash };

place in RAM_region          { block ApplicationRam };

place in STACK_region        { block CSTACK };

當(dāng)然如果你希望 IDE 幫你自動初始化 data,bss,textrw 段,那么可以用下面語句替換 initialize manually 語句。

initialize by copy { readwrite, section .textrw };

設(shè)置好初始化方法后,便是放置 section 集合塊了,放置方法主要有兩種,place in 和 place at,前者用于指定空間塊放置(不指定具體地址),后者是指定具體地址放置。

至此一個基本的 linker 文件便大功告成了,是不是 so easy?

番外一、自定義 section

有耐心看到這里的朋友,痞子衡必須得放個大招獎勵一下,前面講的都是怎么處理系統(tǒng)默認段,那么有沒有可能在代碼里自定義段呢?想象一下你有這樣的需求,你需要在你的應(yīng)用里開辟一塊 1KB 的可更新的數(shù)據(jù)區(qū),你想把這個數(shù)據(jù)區(qū)指定到地址 0x18000 - 0x183ff 的范圍內(nèi),你需要在應(yīng)用里定義 4 Byte 的只讀 config block 常量指向這個可更新數(shù)據(jù)區(qū)首地址(這段 config block 只會被外部 debugger 或者 bootloader 更新),如何做到?

// C 文件中

/////////////////////////////////////////////////////

// 用@操作符指定變量 myConfigBlock[4]放進自定義 .myBuffer section

const uint8_t myConfigBlock[4] @ ".myBuffer" = {0x00, 0x01, 0x02, 0x03};

// Linker 文件中

/////////////////////////////////////////////////////

// 自定義指定的 mySection_region,并把 .myBuffer 放到這個 region

define region mySection_region = mem:[from  0x0x18000 to 0x183ff];

place at start of mySection_region { readonly section .myBuffer };

上面做到了將代碼中的常量放入自定義段?,那么怎么將代碼中的函數(shù)也放進自定義段呢?繼續(xù)看下去

// C 文件中

/////////////////////////////////////////////////////

// 用#pragma location 指定函數(shù) myFunction()放進自定義 .myTask section

#pragma location = ".myTask"

void myFunction(void)

{

    __NOP();

}

// Linker 文件中

/////////////////////////////////////////////////////

// 把 .myTask 放到 mySection_region

place in mySection_region { readonly section .myTask };

看起來大功告成了,最后還有一個注意事項,如果 myConfigBlock 在代碼中并未被引用,IDE 在鏈接的時候可能會忽略這個變量(IDE 認為它沒用,所以優(yōu)化了),那么怎么讓 IDE 強制鏈接 myConfigBlock 呢?IAR 留了個后門,在 options->Linker->Input 選項卡中的 Keep symbols 輸入框里填入你想強制鏈接的對象名(注意是代碼中的對象名,而非 linker 文件中的自定義段名)即可。

Note:關(guān)于番外內(nèi)容的更多細節(jié)請查閱 IAR 軟件安裝目錄下 IAR SystemsEmbedded Workbench

xxxarmdocEWARM_DevelopmentGuide.ENU.pdf 文檔里的 Pragma directives 一節(jié)。

至此,嵌入式開發(fā)里的 linker 文件痞子衡便介紹完畢了,掌聲在哪里~~~

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

碩士畢業(yè)于蘇州大學(xué)電子信息學(xué)院,目前就職于恩智浦(NXP)半導(dǎo)體MCU系統(tǒng)部門,擔(dān)任嵌入式系統(tǒng)應(yīng)用工程師。痞子衡會定期分享嵌入式相關(guān)文章