鏈接腳本(Linker Scripts)語法和規則解析(自官方手冊)

来源:https://www.cnblogs.com/jianhua1992/archive/2022/11/02/16852784.html
-Advertisement-
Play Games

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-web ...


 為了便於與英文原文對照學習與理解(部分翻譯可能不准確),本文中的每個子章節標題和引用使用的都是官方手冊英文原稱。命令及命令行選項統一使用斜體書寫。高頻小節會用藍色字體標出。

3 Linker Scripts

每個鏈接都由一個鏈接腳本控制。該腳本使用鏈接器命令語言編寫。

鏈接腳本的主要目的是描述如何將輸入文件中的各個部分映射到輸出文件中,並控制輸出文件的記憶體佈局。大多數鏈接腳本僅此而已。但是,必要時,鏈接器腳本也可以使用下麵描述的命令來指導鏈接器執行更多的其它操作。

鏈接器通常使用一個鏈接腳本。如果沒有為其提供,鏈接器將會使用預設的編譯在鏈接器執行文件內部的腳本。可以使用命令verbose 顯示預設的鏈接腳本。某些命令行選項,例如 ’-r ’,’-N ’ 會影響預設的鏈接腳本。

你可以通過在命令行使用-T 命令使用自己的腳本。如果使用此命令,你的鏈接腳本將會替代預設鏈接腳本。

也可以通過將腳本作為鏈接器輸入文件隱式的使用鏈接腳本,參考Implicit Linker Scripts

  • Basic Script Concepts: 鏈接器腳本的基本概念
  • Script Format: 鏈接器腳本的格式
  • Simple Example: 簡單的鏈接器腳本例子
  • Simple Commands: 簡單的鏈接器腳本命令
  • Assignments: 為符號指定數值
  • SECTIONS: 段命令
  • MEMORY: 記憶體命令
  • PHDRS: PHDRS命令
  • VERSION: 版本命令
  • Expressions: 鏈接腳本的表達式
  • Implicit Linker Scripts: 隱式鏈接腳本

3.1 Basic Linker Script Concepts

為了描述鏈接腳本語言,我們需要定義一些基本概念和辭彙。

鏈接器將輸入文件(一個或多個)合併為一個輸出文件。輸出文件和每個輸入文件都採用一種特殊的數據格式,稱為目標文件格式。每個文件稱為目標文件。輸出文件通常稱為可執行文件,但出於我們的目的,我們也將其稱為目標文件。每個目標文件都有一個段(section)列表。有時把輸入文件的段稱作輸入段,類似的,輸出文件的段稱作輸出段。

目標文件中的每個段都有名稱和大小。大多數段還具有關聯的數據塊,稱為段內容。一個段可能被標記為可載入(loadable),這意味著在運行輸出文件時,段內容需要先載入到記憶體中。一個沒有內容的段是可分配的,這意味著應該在記憶體中預留一個區域,但是這裡不需要載入任何東西(在某些情況下,該記憶體必須清零)。既不可裝載也不可分配的部分通常包含某種調試信息。

每個可載入或可分配的輸出段都有兩個地址。第一個是 VMA 或稱為 虛擬記憶體地址 。這是運行輸出文件時該段將具有的地址。第二個是 LMA ,即 載入記憶體地址 。這是段將會被載入的地址。在大多數情況下,這兩個地址是相同的。當然它們也可能不同,一個示例是將數據段載入到ROM中,然後在程式啟動時將其複製到RAM中(此技術通常用於初始化基於ROM的系統中的全局變數)。在這種情況下,ROM地址將是LMA,而RAM地址將是VMA。

您可以將 objdump程式與 ’ -h '選項一起使用,以查看目標文件中的各個部分。

每個目標文件還具有一個符號列表,稱為符號表。符號可以是定義的也可以是未定義的。每個符號都有一個名稱,每個定義的符號都有一個地址,以及其他信息。如果將C或C ++程式編譯到目標文件中,則將會將所有定義過的函數和全局變數以及靜態變數作為已定義符號。輸入文件中引用的每個未定義函數或全局變數都將成為未定義符號。

您可以使用 nm 程式或帶有 ‘-t’ 選項的 objdump 程式在目標文件中查看符號。

3.2 Linker Script Format

鏈接腳本是文本文件。

一個鏈接器腳本是一系列的命令。每個命令都是一個關鍵字,可能後面還跟有一個參數,或者一個符號的賦值。使用分號分割命令,空格通常被忽略。

類似於文件名或者格式名的字串可以直接輸入。如果文件名含有一個字元例如逗號(逗號被用來分割文件名),你可以將文件名放在雙引號內部。 但是禁止在文件名內使用雙引號字元

你可以像C語言一樣在鏈接腳本內包含註釋,由’/’和’/’劃分。和C一樣,註釋在句法上被當作空格。

3.3 Simple Linker Script Example

大多數的鏈接腳本非常簡單。

最簡單的鏈接腳本只有一個命令:’SECTIONS ’ 。 您可以使用 ’SECTIONS ’ 命令來描述輸出文件的記憶體佈局。

SECTIONS ’ 命令功能非常強大。 在這裡,我們將描述它的一個簡單用法。 假設您的程式僅包含代碼,初始化數據和未初始化數據。 它們分別位於“ .text ”,“.data ”和“ .bss ”段中。 我們進一步假設這些是唯一將會出現在輸入文件中的段。

在此示例中,假設代碼應在地址 0x10000 處載入,數據應從地址 0x8000000 開始。下麵的鏈接腳本將會執行如下操作:

SECTIONS
{
  . = 0x10000;
  .text : { *(.text) }
  . = 0x8000000;
  .data : { *(.data) }
  .bss : { *(.bss) }
}

您將 ’SECTIONS ’ 命令作為關鍵字 ’SECTIONS ’ 編寫,然後在花括弧中包含一系列符號的賦值和輸出段的描述。

上例中 ’SECTIONS ’ 命令中的第一行設置特殊符號 “. ” 的值,即位置計數器。如果未通過其他方式指定輸出段的地址(稍後將介紹其他方式),地址就會被設置為位置計數器的當前值。然後將位置計數器增加輸出段的大小。在‘SECTIONS ’命令的開頭,位置計數器的值為 ‘ 0 ’ 。

第二行定義了一個輸出段“ .text ”。 冒號是必需的語法 ,現在可以忽略它。在輸出段名稱後面的花括弧中,列出應放置在此輸出段中的輸入段的名稱。 “ ” 是與任何文件名匹配的通配符。表達式 ‘ *(.text) ’ 表示所有輸入文件中的所有 ‘.text*’ 輸入段。

由於在定義輸出段 ‘.text’ 時位置計數器為‘0x10000 ’,因此鏈接程式會將輸出文件中 ‘.text’ 段的地址設置為‘0x10000 ’。

剩下的行定義了定義輸出文件中的‘.data ’ 和‘.bss ’ 段。鏈接器會將‘.data ’ 輸出段放置在地址’0x8000000 ’處。在鏈接器放置‘.data ’ 段後,位置計數器為’0x8000000 ’加上‘.data ’ 段的大小。因此‘.bss ’ 輸出段在記憶體中將會緊緊挨在‘.data ’段後面。

鏈接器將通過增加位置計數器(如有必要)來確保每個輸出部分具有所需的對齊方式。在此示例中, ‘.text’ 和‘.data ’ 段的指定地址可以滿足任何對齊方式約束,但鏈接器可能必須在‘.data ’ 和‘.bss ’ 段之間創建一個小的間隙。

如上,這就是一個簡單完整的鏈接腳本。

3.4 Simple Linker Script Commands

在本節中,我們將介紹一些簡單的鏈接腳本命令。

  • Entry Point : 設置入口點
  • File Commands : 處理文件的命令
  • Format Commands : 處理目標文件格式的命令
  • REGION_ALIAS : 為記憶體區域分配別名
  • Miscellaneous Commands : 其它鏈接腳本命令

3.4.1 Setting the Entry Point

在程式中執行的第一條指令稱為入口點。 您可以使用 ENTRY 鏈接器腳本命令來設置入口點。 參數是符號名稱:

ENTRY(symbol)

有幾種設置入口點的方法。 鏈接器將通過依次嘗試以下每種方法來設置入口點,併在其中一種成功後停止:

  • -e ’輸入命令行選項;
  • 鏈接描腳本中的 ENTRY(symbol) 命令;
  • 目標專用符號值(如果已定義); 對於許多目標來說是 start 符號,但是例如基於PE和BeOS的系統檢查可能的輸入符號列表,並與找到的第一個符號匹配。
  • .text ’ 部段的第一個位元組的地址(如果存在);
  • 地址0。

3.4.2 Commands Dealing with Files

以下是鏈接器腳本處理文件的幾個常用命令:
(1)INCLUDE filename
在命令處包含鏈接腳本文件 filename ,將在當前目錄以及 -L 選項指定的任何目錄中搜索文件。INCLUDE 調用嵌套最多10個級別。

可以直接把 INCLUDE 放到頂層、 MEMORY 或者 SECTIONS 命令中,或者在輸出段的描述中。

(2)INPUT(file, file, …) / INPUT(file file …)
INPUT 命令指示鏈接程式在鏈接中包含指定的文件,就好像它們是在命令行上命名的一樣。

例如,如果您始終希望在每次執行鏈接時都包含 subr.o,但又不想將其放在每個鏈接命令行中,則可以在鏈接腳本中放置 ‘INPUT (subr.o) ’。

實際上,您可以在鏈接描述文件中列出所有輸入文件,然後僅用‘-T ’選項調用鏈接腳本。

如果配置了sysroot 首碼,且文件名以‘/ ’符開頭,並且正在處理的腳本位於sysroot 首碼內,則將在sysroot 首碼中查找文件名。也可以通過指定 = 作為文件名路徑中的第一個字元,或在文件名路徑前加上 $ SYSROOT 來強制使用sysroot 首碼。另請參閱命令行選項中對‘-L ’ 的描述(Command-line Options)。

如果未使用 sysroot 首碼,則鏈接器將嘗試打開包含鏈接器腳本的目錄中的文件。如果沒有找到,鏈接器將搜索當前目錄。如果仍未找到,鏈接器將搜索庫的搜索路徑。

如果您使用 ‘INPUT (-lfile) ’ ,則 ld 會將名稱轉換為 libfile.a,就像命令行參數‘-l ’一樣。

當您在隱式鏈接腳本中使用 INPUT 命令時,文件在鏈接腳本文件被包含的時刻才會被加入。這可能會影響庫的搜索。

(3)GROUP(file, file, …) / GROUP(file file …)
GROUP 命令類似於 INPUT,不同之處在於,所有file指出的名字都應該為庫,並且所有庫將會被重覆搜索直到沒有新的未定義引用被創建。 請參閱命令行選項中 ‘-(’ 的說明(Command-line Options)。

(4)AS_NEEDED(file, file, …) / AS_NEEDED(file file …)
此構造只能出現在 INPUTGROUP 命令以及其他文件名中。命令中的文件將會以類似於直接出現在 INPUTGROUP 命令中的文件一樣處理,除了ELF共用庫,ELF共用庫僅在真正需要使用時才被添加。這個構造實質上為其中列出的所有文件啟用了 -as-needed 選項,為了恢復以前編譯環境,之後需設置 --no-as-needed。

(5)OUTPUT(filename)
OUTPUT 命令為輸出文件命名。 在鏈接腳本中使用 OUTPUT(filename)與在命令行中使用 ‘-o filename’ 一樣(請參閱Command-line Options)。 如果兩者都使用,則命令行選項優先。

您可以使用 OUTPUT 命令為輸出文件定義預設名稱,以此替代預設名稱a.out。

(6)SEARCH_DIR(path)
SEARCH_DIR 命令添加一個 ld 搜索庫的路徑。使用 SEARCH_DIR(path) 與在命令行上使用 ’ -L path ’ 完全一樣(參見Command-line Options)。如果同時使用了這兩條路徑,那麼鏈接器將會搜索所有路徑。首先搜索使用命令行選項指定的路徑。

(7)STARTUP(filename)
STARTUP 命令與 INPUT 命令類似,除了filename將成為要鏈接的第一個輸入文件,就像它是在命令行中首先指定的一樣。在一些把第一個文件當作入口點的系統上這個命令非常有效。

3.4.3 Commands Dealing with Object File Formats

有兩個鏈接器腳本命令可以用來處理對象文件格式:

OUTPUT_FORMAT(bfdname)
OUTPUT_FORMAT(default, big, little)

OUTPUT_FORMAT 命令使用BFD格式的命名方式(請參見BFD)。使用 OUTPUT_FORMAT(bfdname) 與在命令行上使用 ‘–oformat bfdname ’ 完全相同(請參見Command-line Options)。如果兩者都使用,則命令行選項優先。

您可以將OUTPUT_格式與三個參數一起使用,以根據 ’ -EB ’ 和 ‘-EL’ 命令行選項使用不同的格式。這允許鏈接器腳本根據所需的endianness設置輸出格式。

如果未使用 ’ -EB ’ 和 ‘-EL’ ',那麼輸出格式將會使用第一個參數作為預設值。如果使用 ’ -EB ',輸出格式將是第二個參數 big。如果使用 ‘-EL’ ',輸出格式將是第三個參數,little

例如,MIPS ELF目標的預設鏈接器腳本使用以下命令:

OUTPUT_FORMAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)

這說明輸出文件的預設格式是 ‘elf32-bigmips’,但如果用戶使用’-EL’ '命令行選項,則將以‘elf32-littlemips’格式創建輸出文件。

TARGET(bfdname)

TARGET 命令設置讀取輸入文件時的BFD格式。這將影響後面的 INPUTGROUP 命令。此命令類似使用命令行指令 ‘-b bfdname’ (參見Command-line Options)。如果使用了TARGET命令,但OUTPUT_FORMAT命令沒使用,則最後的TARGET命令還被用來設置輸出文件的格式(參見BFD)。

3.4.4 Assign alias names to memory regions

可以為MEMORY命令創建的記憶體區域提供別名。 每個名稱最多對應一個存儲區域

REGION_ALIAS(alias, region)

REGION_ALIAS 函數為 記憶體區域創建別名 。這允許靈活地將輸出部分映射到記憶體指定區域。下麵有一個例子。

假設我們有一個用於嵌入式系統的應用程式,它帶有各種記憶體存儲設備。它們都有一個通用的,易失性記憶體RAM,允許代碼執行或數據存儲。一些可能有一個只讀的、非易失性記憶體ROM,允許代碼執行和只讀數據訪問。最後一個是只讀、非易失性存儲器ROM2,允許對只讀數據段讀取,不允許代碼執行。現在有四個輸出段:

  • .text :程式代碼
  • .rodata :只讀數據
  • .data :可讀寫且需要初始化數據
  • .bss :可讀寫的置零初始化數據
    目標是提供一個鏈接器腳本文件,該文件包含定義系統無關的輸出段的部分,和將輸出段映射到系統上可用記憶體區域的系統相關部分。我們的嵌入式系統有三種不同的記憶體設置A、B和C:
Section	Variant A	Variant B	Variant C
.text	RAM	         ROM	    ROM
.rodata	RAM	         ROM	    ROM2
.data	RAM	         RAM/ROM	RAM/ROM2
.bss	RAM	         RAM	    RAM

RAM/ROM或RAM/ROM2表示將此段分別載入到區域ROM或ROM2中。請註意,三個設置的.data段的起始地址都位於.rodata段的末尾。

接下來是處理輸出段的基本鏈接腳本。 它包含描述記憶體佈局的系統相關鏈接 cmds.memory 文件:

INCLUDE linkcmds.memory

SECTIONS
{
.text :
{
(.text)
} > REGION_TEXT
.rodata :
{
(.rodata)
rodata_end = .;
} > REGION_RODATA
.data : AT (rodata_end)
{
data_start = .;
(.data)
} > REGION_DATA
data_size = SIZEOF(.data);
data_load_start = LOADADDR(.data);
.bss :
{
(.bss)
} > REGION_BSS
}

現在我們需要三個不同的 linkcmds.memory 來定義記憶體區域以及別名。下麵是A,B,C不同的 linkcmds.memory

A :所有都存入RAM

MEMORY
  {
    RAM : ORIGIN = 0, LENGTH = 4M
  }

REGION_ALIAS("REGION_TEXT", RAM);
REGION_ALIAS("REGION_RODATA", RAM);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);

B :代碼和只讀數據存入ROM。可讀寫數據放入RAM。一個已初始化了的數據的鏡像被載入到ROM,併在系統啟動的時候讀入RAM

MEMORY
  {
    ROM : ORIGIN = 0, LENGTH = 3M
    RAM : ORIGIN = 0x10000000, LENGTH = 1M
  }

REGION_ALIAS("REGION_TEXT", ROM);
REGION_ALIAS("REGION_RODATA", ROM);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);

C :代碼放入ROM,只讀數據放入ROM2。可讀寫數據放入RAM。一個已初始化了的數據的鏡像被載入到ROM2,併在系統啟動的時候讀入RAM

MEMORY
  {
    ROM : ORIGIN = 0, LENGTH = 2M
    ROM2 : ORIGIN = 0x10000000, LENGTH = 1M
    RAM : ORIGIN = 0x20000000, LENGTH = 1M
  }

REGION_ALIAS("REGION_TEXT", ROM);
REGION_ALIAS("REGION_RODATA", ROM2);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);

如有必要,可以編寫通用的系統初始化程式以將.data段從ROM或ROM2複製到RAM:

#include <string.h>

extern char data_start [];
extern char data_size [];
extern char data_load_start [];

void copy_data(void)
{
if (data_start != data_load_start)
{
memcpy(data_start, data_load_start, (size_t) data_size);
}
}

3.4.5 Other Linker Script Commands

還有一些其他鏈接器腳本命令:

  • ASSERT(exp, message)
    註意此斷言會在最終鏈接階段之前進行檢查。這表示,在段內使用PROVIDE的定義如果用戶沒有為其設置值,此表達式將無法通過檢測。唯一的例外是PROVIDE的符號剛剛引用了’.’。因此,一個如下斷言:
    確保 exp 不為零。 如果為零,則退出鏈接並顯示錯誤代碼,並列印一些相關的信息

請註意,在鏈接的最後階段發生之前會檢查斷言。 這意味著,如果用戶沒有為這些符號設置值,則涉及段定義中提供的符號的表達式將失敗。 該規則的唯一例外是僅引用點的提供的符號。 因此,這樣的斷言:

.stack :
  {
    PROVIDE (__stack = .);
    PROVIDE (__stack_size = 0x100);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
  }

如果沒有在其他地方定義stack_size,則會失敗。在段外定義的符號會在此前被求值,可以在ASSERTions 使用它們,因此:

PROVIDE (__stack_size = 0x100);
  .stack :
  {
    PROVIDE (__stack = .);
    ASSERT ((__stack > (_end + __stack_size)), "Error: No room left for the stack");
  }

將會工作。

  • EXTERN(symbol symbol …)
    強制將符號作為未定義符號輸入到輸出文件中。 這樣做可能會例如觸發標準庫中其他模塊的鏈接。 您可以為每個 EXTERN 列出幾個符號,並且可以多次使用 EXTERN。 此命令與 ‘-u ’ 命令行選項具有相同的作用。

  • FORCE_COMMON_ALLOCATION
    這個命令與’ -d ’ 命令行選項具有相同的效果:即便是使用了’-r’ 的重定位輸出文件,也讓 ld 為普通符號分配空間。

  • INHIBIT_COMMON_ALLOCATION
    這個命令與命令行選項 ‘–no-define-common’ 具有相同的效果 : 讓 ld 不為普通符號分配空間,即便是一個非可重定位輸出文件。

  • FORCE_GROUP_ALLOCATION
    這個命令與命令行選項 ‘–force-group-allocation’ 具有相同的效果 : 使ld place 段組成員像普通的輸入段一樣,並且即使指定了可重定位的輸出文件(’ -r ')也可以刪除段組。

  • INSERT [ AFTER | BEFORE ] output_section
    此命令通常在‘-T ’ 指定的腳本中使用,用來增強預設的SECTIONS。例如,重覆占位程式段。它將把所有此前的鏈接腳本的聲明插入output_section的後面(或者前面),並且使 ’-T ’不要覆蓋預設鏈接腳本。實際插入點類似於孤兒段。參見Location Counter。插入發生在鏈接器把輸入段映射到輸出段後。在插入前,因為’-T ’的腳本在預設腳本之前被解析,在’-T’腳本中的聲明會先於預設內部腳本的聲明而執行。特別是,將對預設腳本中的’-T ’輸出段進行輸入段分配。下例為’-T ’腳本使用INSERT可能的情況:

SECTIONS
{
  OVERLAY :
  {
    .ov1 { ov1*(.text) }
    .ov2 { ov2*(.text) }
  }
}
INSERT AFTER .text;
  • NOCROSSREFS(section section …)
    此命令可能被用來告訴 ld,如果引用了section的參數就報錯。

在特定的程式類型中,比如使用覆蓋技術的嵌入式系統,當一個段被載入到記憶體中,另一個段不會被載入。任何兩個段之間直接的引用都會帶來錯誤。例如,如果一個段中的代碼調用另一個段中的函數,將會產生錯誤。

NOCROSSREFS 命令列出了一系列輸出段的名字。如果 ld 檢測到任何段間交叉引用,將會報告錯誤並返回非零退出碼。註意NOCROSSREFS使用輸出段名稱,而不是輸入段名稱。

  • NOCROSSREFS_TO(tosection fromsection …)
    此命令可能被用來告訴 ld,從其他段列表中對某個段的任何引用就會引發錯誤。

當需要確保兩個或多個輸出段完全獨立,但是在某些情況下需要單向依賴時,NOCROSSREFS 命令很有用。 例如,在多核應用程式中,可能存在可以從每個核調用的共用代碼,但是出於安全考慮,絕不能回調。

NOCROSSREFS_TO 命令攜帶(給出)輸出段名稱的列表。 其他任何部分都不能引用第一部分。 如果 ld 從其他任何部分中檢測到對第一部分的任何引用,它將報告錯誤並返回非零退出狀態。 請註意,NOCROSSREFS_TO 命令使用輸出段名稱,而不是輸入段名稱。

  • OUTPUT_ARCH(bfdarch)
    指定一個特定的輸出機器架構。該參數是BFD庫使用的名稱之一(請參閱BFD)。通過使用帶有 ’ -f ’ 選項的objdump程式,您可以看到目標文件的體繫結構。

  • LD_FEATURE(string)
    此命令可用於修改 ld 行為。如果字元串是“SANE_EXPR”,那麼腳本中的絕對符號和數字將被在任何地方當作數字對待。請參考 Expression Section

3.5 Assigning Values to Symbols

可以給鏈接器腳本中的符號賦值。這會定義符號並將其放入具有全局作用域的符號表中。

  • Simple Assignments 簡單賦值
  • HIDDEN 隱藏
  • PROVIDE PROVIDE
  • PROVIDE_HIDDEN PROVIDE_HIDDEN
  • Source Code Reference 如何在源代碼中使用一個鏈接腳本定義的符號

3.5.1 Simple Assignments

您可以使用任何C賦值操作符來賦值符號:

symbol = expression ;
symbol += expression ;
symbol -= expression ;
symbol *= expression ;
symbol /= expression ;
symbol <<= expression ;
symbol >>= expression ;
symbol &= expression ;
symbol |= expression ;

第一種情況將表達式的值賦值給符號。 在其他情況下,必須先定義符號,並相應地調整符號的值。

特殊符號名稱 ‘. ’ 表示位置計數器。 您只能在 SECTIONS 命令中使用它。 請參閱 Location Counter

表達式後面的分號不能省略。

表達式定義如下; 請參閱Expressions

你在寫表達式賦值語句時,可以把它們作為單獨的部分,也可以作為 ’SECTIONS’ 命令中的一個語句,或者作為 ’SECTIONS’ 命令中輸出段描述的一個部分。

符號的有效作用區域由表達式所在的段決定,Expression Section

下麵是是三個不同位置為符號賦值的示例:

floating_point = 0;
SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
    }
  _bdata = (. + 3) & ~ 3;
  .data : { *(.data) }
}

在本例中,符號 ‘floating_point’ 將被定義為零。符號 ’ _etext ’ 將被設置為緊隨 ’.text’ 最後一個輸入段後面的地址。符號’ _bdata '將被定義為在 ’.text’ 輸出段後面的一個4位元組向上對齊的地址。

3.5.2 HIDDEN

語法HIDDEN(symbol = expression)為ELF目標的埠定義一個符號,符號將被隱藏並且不會被導出。
下麵是Simple Assignments的例子,使用HIDDEN重寫:

HIDDEN(floating_point = 0);
SECTIONS
{
  .text :
    {
      *(.text)
      HIDDEN(_etext = .);
    }
  HIDDEN(_bdata = (. + 3) & ~ 3);
  .data : { *(.data) }
}

在本例中,這三個符號在此模塊之外都不可見

3.5.3 PROVIDE

在某些情況下,僅當一個符號被引用了卻沒有定義在任何鏈接目標中,才需要為鏈接腳本定義一個符號。例如,傳統鏈接器定義了符號‘etext’。然而,ANSI C要求用戶能夠使用’ etext '作為函數名而不會引發錯誤。PROVIDE關鍵字可以用來定義一個符號,比如‘etext’ ,只有當它被引用但沒有被定義時才使用。語法是 PROVIDE(symbol = expression)

下麵是一個使用提供定義‘etext’的例子:

SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
      PROVIDE(etext = .);
    }
}

在本例中,如果程式定義了’ _etext ‘(帶有前導下劃線),鏈接器將給出重覆定義錯誤。另一方面,如果程式定義了’ etext ‘(沒有前導下劃線),鏈接器會預設使用程式中的定義。如果程式引用了’ etext '但沒有定義它,鏈接器將使用鏈接器腳本中的定義。

註意 -PROVIDE指令將考慮定義一個普通符號,即使這樣的符號可以與PROVIDE將創建的符號組合在一起。當考慮構造函數和析構函數列表符號時,這一點尤其重要,因為它們通常被定義為普通符號。

3.5.4 PROVIDE_HIDDEN

PROVIDE 類似。對於ELF目標的埠,符號將被隱藏且不會被輸出。

3.5.5 Source Code Reference

從源代碼獲取鏈接器腳本定義的變數並不直觀。 特別是,特別是鏈接腳本中的符號與高級語言定義的變數聲明不同的時候,將使用一個沒有值的變數替代它。

在進一步討論之前,必須註意,當編譯器將源代碼中的名稱存儲在符號表中時,它們通常會將它們轉換為不同的名稱。 例如,Fortran編譯器通常在前面或後面加上下劃線,而C ++則執行大量的 ‘name mangling ’。 因此,在源代碼中使用的變數名稱與在鏈接腳本中定義的相同變數的名稱之間可能會有差異。 例如,在C語言中,鏈接腳本變數可能稱為:

extern int foo;

但是在鏈接器腳本中,它可能被定義為:

  _foo = 1000;

然而,在其餘的例子中,假設沒有發生名稱轉換。

當一個符號用高級語言,比如C語言,聲明瞭一個符號,會發生兩件事。首先,編譯器在程式記憶體中保留足夠的空間來保存符號的值。第二種方法是編譯器在程式的符號表中創建一個條目,用來保存符號的地址。例如下麵的C聲明:

 int foo = 1000;

在符號表中創建一個名為’ foo '的條目。這個入口保存了一個‘int’ 大小的記憶體塊的地址,數字1000最初存儲在這裡。

當程式引用一個符號時,編譯器生成的代碼首先訪問符號表以查找該符號的記憶體塊地址,然後代碼從該記憶體塊讀取值。所以:

 foo = 1;

在符號表中查找符號’ foo ',獲取與該符號關聯的地址,然後將值1寫入該地址。而:

int * a = & foo;

在符號表中查找符號’ foo ',獲取它的地址,然後將這個地址複製到與變數 ’ a ’ 相關聯的記憶體塊中。

相比之下,鏈接器腳本符號聲明在符號表中創建一個條目,但不給它們分配任何記憶體。因此,它們是一個沒有值的地址。例如鏈接器腳本定義:

  foo = 1000;

在符號表中創建一個名為’ foo '的條目,該條目保存記憶體位置1000的地址,但地址1000上沒有存儲任何特殊內容。這意味著您無法訪問鏈接程式腳本定義的符號的值-它沒有值。您所能做的就是訪問鏈接器腳本定義符號的地址。

因此,當您在源代碼中使用鏈接器腳本定義的符號時,您應該始終獲取該符號的地址,並且永遠不要嘗試使用它的值。例如,假設你想把記憶體的 .ROM 拷貝到 .FLASH 中,鏈接器腳本包含了這些聲明:

  start_of_ROM   = .ROM;
  end_of_ROM     = .ROM + sizeof (.ROM);
  start_of_FLASH = .FLASH;

那麼執行複製的C源代碼為:

  extern char start_of_ROM, end_of_ROM, start_of_FLASH;

memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);

註意 ‘&’ 運算符的使用。上面是正確的代碼。一種替換是,把符號被當作一個數組變數的名稱,因此代碼變成了:

  extern char start_of_ROM[], end_of_ROM[], start_of_FLASH[];

memcpy (start_of_FLASH, start_of_ROM, end_of_ROM - start_of_ROM);

註意此時不需要操作符 ’&’ 了。

3.6 SECTIONS Command

SECTIONS 命令告訴鏈接器如何將輸入段映射到輸出段,以及如何將輸出段放在記憶體中。
SECTIONS 命令的格式為:

SECTIONS
{
  sections-command
  sections-command
  …
}

每個 sections-command 命令可能是下麵之一:

為了方便在這些命令中使用位置計數器,在SECTIONS 命令中允許使用 ENTRY 命令和符號賦值。 這也可以使鏈接描述文件更容易理解,因為你可以在更有意義的地方使用這些命令來控制輸出文件的佈局。

輸出段描述和覆蓋在下麵將會分析。

如果在鏈接腳本中未使用 SECTIONS 命令,則鏈接器將會照輸入文本的順序,將每個輸入部段放置到名稱相同的輸出段中。例如,如果所有輸入段出現在第一個文件中,輸出文件的段的順序將會與第一個輸入文件保持一致。第一個段被放在地址0。

  • Output Section Description 輸出段描述
  • Output Section Name 輸出段名稱
  • Output Section Address 輸出段地址
  • Input Section 輸入段描述
  • Output Section Data 輸出段數據
  • Output Section Keywords 輸出段關鍵字
  • Output Section Discarding 輸出段忽略的內容
  • Output Section Attributes 輸出段屬性
  • Overlay Description Overlay description

3.6.1 Output Section Description

輸出段的完整描述如下所示:

section [address] [(type)] :
  [AT(lma)]
  [ALIGN(section_align) | ALIGN_WITH_INPUT]
  [SUBALIGN(subsection_align)]
  [constraint]
  {
    output-section-command
    output-section-command
    …
  } [>region] [AT>lma_region] [:phdr :phdr …] [=fillexp] [,]

大部分的可選段屬性在多數輸出段不需要使用。

SECTION 邊上的空格是必須的,這樣段名就沒有歧義了。冒號和花括弧也是必需的。如果使用了fillexp,並且下一個section -命令看起來像是表達式的延續,則可能需要在末尾使用逗號。換行符和其他空格是可選的。

當 fillexp 使用且接下來的 sections-command 看起來像是表達式的延續的時候,可能需要在後面加上逗號。

每個 output-section-command 可以是下列命令之一:
符號賦的值(參見Assignments)
輸入段描述(參見Input Section)
直接包引用的數據值(參見Output Section Data)
特殊的輸出段關鍵字(參見Output Section Keywords))

3.6.2 Output Section Name

輸出段的名字是 sectionsection必須滿足輸出格式的規定。在只支持有限段數目的格式中,例如 a.out ,名稱必須是該格式所支持的名稱之一(例如a.out ,只允許’.text’,’.data’,’.bss’)。如果輸出格式支持任意數量的段,但是只有數字而不是名稱(Oasys 就是這種情況),則名稱應該以帶引號的數字字元串的形式提供。一個段的名字可以由任意字元序列組成,但一個含有許多特殊字元(如逗號)的名稱必須用引號括起來。

名稱為 ‘/DISCARD/’ 的輸出段 ,有特殊含義; 參考Output Section Discarding.

3.6.3 Output Section Address

address 是輸出段VMA(虛擬記憶體地址)的表達式。此地址是可選參數,但如果提供了該地址,則輸出地址就會被精確的設置為指定的值。

如果沒有指定輸出地址,那麼則依照下麵的幾種方式嘗試選擇一個地址。此地址將被調整以適應輸出段的對齊要求。輸出段的對齊要求是所有輸入節中含有的對齊要求中最嚴格的一個。

輸出段地址探索如下:

  • 如果為該段設置了一個輸出記憶體區域,那麼它將被添加到該區域中,其地址將是該區域中的下一個空閑地址。

  • 如果使用 MEMORY 命令創建記憶體區域列表,那麼將選擇具有與該段相容屬性的第一個區域來包含該區域。該部分的輸出地址將是該區域中的下一個空閑地址;MEMORY

  • 如果沒有指定記憶體區域,或者沒有與段匹配的記憶體區域,則輸出地址將基於位置計數器的當前值。

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

-Advertisement-
Play Games
更多相關文章
  • 作者:牛牛碼特 鏈接:https://juejin.cn/post/6844903929281511438 背景 緩存是軟體開發中一個非常有用的概念,資料庫緩存更是在項目中必然會遇到的場景。而緩存一致性的保證,更是在面試中被反覆問到,這裡進行一下總結,針對不同的要求,選擇恰到好處的一致性方案。 緩存 ...
  • 責任鏈模式(Chain of Responsibility Pattern)是將鏈中每一個節點看作是一個對象,每個節點處理的請求均不同,且內部自動維護一個下一節點對象。 ...
  • 為什麼會寫這篇文章?主要是因為項目中的代碼大量使用了帶virtual關鍵字的類,想通過本文淺談一下。virtual並沒有什麼超能力可以化腐朽為神奇,它有其存在的理由,但濫用它是一種非常不可取的錯誤行為。本文將帶你一步一步瞭解virtual機制,為你揭開virtual的神秘面紗。 ...
  • CentOS6.x CentOS6中轉用Upstrat代替以前的init.d/rcX.d的線性啟動方式。 一、相關命令 通過initctl help可以查看相關命令 [root@localhost ~]# initctl help Job commands: start Start job. sto ...
  • 背景 2008 年前後的 Midori 項目試圖構建一個以 .NET 為用戶態基礎的操作系統,在這個項目中有很多讓 CLR 以及 C# 的類型系統向著適合系統編程的方向改進的探索,雖然項目最終沒有面世,但是積累了很多的成果。近些年由於 .NET 團隊在高性能和零開銷設施上的需要,從 2017 年開始 ...
  • 自己寫了一種,速度不是很快,但是能夠實現 var findpic = new FindPic(); var rec = findpic.FindPicture(@"C:\Users\zaranet\Desktop\xiao.png", @"C:\Users\zaranet\Desktop\da.pn ...
  • ###前言 剛接觸XAF的小伙伴可能會有一個疑惑,XAF中有Model(BusinessObject)、View、Controller,感覺明顯是一個MVC的設計模式,但當你用MVC的設計模式與其對應時,又會發現有一些不一樣,可能這時有小伙伴會想會不會是MVC的變體,因為MVC只是一個設計模式,不同 ...
  • 長連接與短連接 所謂長連接,指在一個TCP連接上可以連續發送多個數據包,在TCP連接保持期間,如果沒有數據包發送,需要雙方發檢測包以維持此連接,一般需要自己做線上維持。 短連接是指通信雙方有數據交互時,就建立一個TCP連接,數據發送完成後,則斷開此TCP連接,一般銀行都使用短連接。 比如http的, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...