豹哥嵌入式講堂:ARM開發之文件詳解(2)- linker文件

来源:https://www.cnblogs.com/henjay724/archive/2018/01/04/8191908.html
-Advertisement-
Play Games

linker文件是在鏈接階段所要用到的文件,source文件在編譯過程完成之後,需要再經過鏈接器從而將二進位數據有序組織起來形成最終的二進位可執行文件,linker文件就是用來指示鏈接器如何組織編譯生成的二進位數據。 ...



  大家好,我是豹哥,獵豹的豹,犀利哥的哥。今天豹哥給大家講的是嵌入式開發里的linker文件

  在前一節課source文件詳解里,豹哥給大家系統地介紹了source文件,source文件是嵌入式工程里典型的input文件,那麼還有沒有其他類型的input文件?既然豹哥這麼提問了,那答案肯定是有啦。今天豹哥要講的linker文件就屬於另一種input文件。
  linker文件顧名思義就是嵌入式工程在鏈接階段所要用到的文件,source文件在編譯過程完成之後(此時已經是機器可識別的二進位機器碼數據),需要再經過鏈接器從而將二進位數據有序組織起來形成最終的二進位可執行文件,該二進位文件最終會被下載進晶元內部非易失性存儲器里。linker文件就是用來指示鏈接器如何組織編譯生成的二進位數據。
  linker文件是跟IDE息息相關的,本文以IAR EWARM為例介紹linker文件,其他IDE下的linker文件可觸類旁通。

一、 嵌入式系統中的section

  在講linker文件之前,豹哥必須先跟大家理清一個嵌入式系統中很重要的概念-section。那麼什麼是section?我們寫的C或者彙編source文件里都是各種應用代碼,這些代碼按功能可以分為很多種類,比如常量、變數、函數、堆棧等,而相同類型的代碼的集合便是一個section,鏈接器在鏈接時組織數據的基本單元便是section。那麼一個典型的嵌入式系統中到底有多少種section呢?下麵列出了IAR里預設的所有section,那些常見section在後續介紹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 Systems\Embedded Workbench xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文檔里的Section reference一節。

二、解析linker文件

  知道了section概念,那便可開始深入瞭解linker文件,什麼是linker文件?linker文件是按IDE規定的語法寫成的用於指示鏈接器分配各section在嵌入式系統存儲器中存放位置的文件。大家都知道嵌入式系統存儲器主要分為兩類:ROM(非易失性),RAM(易失性),所以相應的這些section根據存放的存儲器位置不同也分為兩類屬性:readonly, readwrite。實際上linker文件的工作就是將readonly section放進ROM,readwrite section放進RAM。
  那麼到底該如何編寫工程的linker文件呢?正如前面所言,linker文件也是有語法的,而且這語法是由IDE指定的,所以必須要先掌握IDE制定的語法規則,linker文件語法規則相對簡單,最常用的關鍵字就是如下8個:

// 動詞類關鍵字
define                // 定義各種空間範圍、長度
initialize            // 設置section初始化方法
place in              // 放置section於某region中(具體地址由鏈接器分配)
place at              // 放置section於某絕對地址處

// 名詞類關鍵字
symbol                // 各種空間範圍、長度的標識
memory                // 整個ARM記憶體空間的標識
region                // 在整個ARM記憶體空間中劃分某region空間的標識
block                 // 多個section的集合塊的標識

Note:上述linker語法的詳細解釋請查閱IAR軟體安裝目錄下\IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文檔里的The linker configuration file一節。

  到這裡我們已經可以開始愉快地寫linker文件了,是不是有點按捺不住了?來吧,只需要三步走,Let's do it。
  此處假設MCU物理空間為:ROM(0x0 - 0x1ffff)、RAM(0x10000000 - 0x1000ffff),豹哥要寫的linker要求如下:

  • 中斷向量表必須放置於ROM起始地址0x0,且必須256位元組對齊
  • STACK大小為8KB,HEAP大小為1KB,且必須8位元組對齊
  • SATCK必須放置在RAM起始地址0x10000000
  • 其餘section放置在正確的region里,具體空間由鏈接器自動分配

2.1 定義物理空間

  第一步我們先定義3塊互不重疊的空間ROM_region、RAM_region、STACK_region,其中ROM_region對應的是真實的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__ + __ICFEDIT_size_ps_rom__ 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__ - __ICFEDIT_size_rom_patch__];

2.2 定義section集合

  第二步是自定義section集合塊,細心的朋友可以看到右邊花括弧里包含的都是上一節介紹的系統預設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對應的section可以分別放進ApplicationFlash和ApplicationRam,那為何多此一舉?仔細閱讀過豹哥前一篇文章source文件詳解的朋友肯定就知道答案了,在那篇文章里介紹的startup.c文件里有一個叫init_data_bss()的函數,這個函數會完成初始化CodeRelocateRam塊的功能,它找尋的就是CodeRelocate段名字,這個名字比系統預設的textrw名字看起來更清晰易懂。

2.3 安置section集合

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

// 設置初始化方法
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 };

  當然如果你希望IDE幫你自動初始化data,bss,textrw段,那麼可以用下麵語句替換initialize manually語句。

initialize by copy { readwrite, section .textrw };

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

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

番外一、自定義section

  有耐心看到這裡的朋友,豹哥必須得放個大招獎勵一下,前面講的都是怎麼處理系統預設段,那麼有沒有可能在代碼里自定義段呢?想象一下你有這樣的需求,你需要在你的應用里開闢一塊1KB的可更新的數據區,你想把這個數據區指定到地址0x18000 - 0x183ff的範圍內,你需要在應用里定義4 Byte的只讀config block常量指向這個可更新數據區首地址(這段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 };

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

// C文件中
/////////////////////////////////////////////////////
// 用#pragma location指定函數myFunction()放進自定義.myTask section
#pragma location = ".myTask"
void myFunction(void)
{
    __NOP();
}

// Linker文件中
/////////////////////////////////////////////////////
// 把.myTask放到mySection_region
place in mySection_region { readonly section .myTask };

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

Note:關於番外內容的更多細節請查閱IAR軟體安裝目錄下\IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf文檔里的Pragma directives一節。

  至此,嵌入式開發里的linker文件豹哥便介紹完畢了,掌聲在哪裡~~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • :setWindowFlags(Qt::CustomizeWindowHint);//設置視窗標題欄自定義 setWindowFlags(Qt::WindowMinimizeButtonHint);//設置視窗的標題欄只有最小化的按鈕 setWindowFlags( Qt::WindowCloseB ...
  • 在日常的運維工作中,經常會用到nginx服務,也時常會碰到nginx因高併發導致的性能瓶頸問題。今天這裡簡單梳理下nginx性能優化的配置(如有不妥,敬請指出~) 一、這裡的優化主要是指對nginx的配置優化,一般來說nginx配置文件中對優化比較有作用的主要有以下幾項:1)nginx進程數,建議按 ...
  • 1,ls 查看當前文件夾下的文件 2,ls -a 查看當前文件夾下的隱藏文件 3,ls -l 查看當前文件夾下的文件,以列表的形式 4,ls -h 查看當前文件夾下的文件,換算大小格式 5,支持寫法:ls -ahl、ls -lha 補充:ls /bin 查看/bin目錄下的內容,此命令無需在bin目 ...
  • 1、在官網下載JDk http://www.oracle.com/technetwork/cn/java/javase/downloads/index.html 如果是64位的操作系統,可以下載64位或者32位的jdk,但是32位的系統不能運行64位的jdk。 2、解壓jdk-x.x.x.tar.g ...
  • 1、安裝PCRE庫 到www.pcre.org 下載pcre-8.37.tar.gz tar -zxvf pcre-8.37.tar.gz cd pcre-8.37 ./configure make make install 2、安裝zlib庫 到www.zlib.net 下載zlib-1.2.8. ...
  • .tar解包:tar xvf FileName.tar打包:tar cvf FileName.tar DirName(註:tar是打包,不是壓縮!)———————————————.gz解壓1:gunzip FileName.gz解壓2:gzip -d FileName.gz壓縮:gzip FileN ...
  • Linux下簡單好用的工具rinetd,實現埠映射/轉發/重定向官網地址http://www.boutell.com/rinetd 軟體下載wget http://www.boutell.com/rinetd/http/rinetd.tar.gz 解壓安裝tar zxvf rinetd.tar.g ...
  • 1、伺服器Git安裝配置 相關鏈接 相關鏈接 註意ssh-keygen 、修改許可權 許可權: 相關鏈接 2、本地獲取 git clone name@ip:伺服器項目位置 相關鏈接 3、創建本地分支推送到遠程 git branch name git push origin name 3.1 伺服器許可權配 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...