qemu-基础篇——链接脚本常用命令

文章目录

  • 链接脚本常用命令
    • ENTRY(symbol),程序入口地址
      • 示例一
      • 示例二
      • 示例三
    • MEMORY
      • 示例一
    • SECTIONS
      • KEEP(保持)
      • PROVIDE
      • AT(LMA_ADDR)
      • ALIGN
    • 参考链接

链接脚本常用命令

ENTRY(symbol),程序入口地址

将符号 symbol 的值设置为入口地址

链接器 (ld) 有多种方法设置程序入口地址,按照一下顺序:(编号越前,优先级越高)

  • ld 命令行 -e 选项
  • 链接脚本中的 ENTRY(symbol) 命令
  • 如果定义了 start 符号, 使用 start 符号值
  • 如果存在 .text section, 使用 .text section 的第一字节的位置值
  • 地址 0

示例一

如果输入文件没有定义,start,可以简单定义一个并给一个合适的值

start = 0x2020;

示例二

上面的示例中使用的是绝对地址,当然,也可以指定为任意合法的表达式。

start = other_symbol;

示例三

实际使用中,可以指定为复位向量地址。

/* Entry Point */
ENTRY(Reset_Handler)

MEMORY

MEMORY 声明一个或多个内存区域,其属性指定该区域是否可以写入、读取或执行。这主要用于不同地址空间区域可能包含不同访问权限的嵌入式系统。

MEMORY
{
    name (attr) : ORIGIN = origin, LENGTH = len
    ...
}
  • name:是链接器在内部用来引用区域的名称。任何可以使用符号名称。区域名称存储在单独的区域中 命名空间,并且不会与符号、文件名或部分冲突。使用不同的名称指定多个区域。
  • attr: 属性
    • R 只读部分。
    • W 读 / 写部分。
    • X 包含可执行代码的部分。
    • A 分配的部分。
    • I 初始化的部分。
    • L 与 I 相同
    • ! 反转以下任何属性的含义。
  • origin: 是物理内存中区域的起始地址, 在之前必须计算为常量表达式
  • len: 是区域的大小(以字节为单位)(表达式)。

示例一

MEMORY
{
    rom (rx)  : ORIGIN = 0, LENGTH = 256K
    ram (!rx) : org = 0x40000000, l = 4M
}

SECTIONS

SECTIONS 命令告诉 ld

  • 如何把输入文件的 sections 映射到输出文件的各个 section: 如何将输入 section 合为输出 section

  • 如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间 (LMA).

SECTIONS
{
    SECTIONS-COMMAND

    SECTIONS-COMMAND
…
}

SECTION-COMMAND 有四种:

  • ENTRY 命令
  • 符号赋值语句
  • 一个输出 section 的描述 (output section description)
  • 一个 section 叠加描述 (overlay description)

如果整个连接脚本内没有 SECTIONS 命令, 那么 ld 将所有同名输入 section 合成为一个输出 section 内, 各输入 section 的顺序为它们被连接器发现的顺序. 如果某输入 section 没有在 SECTIONS 命令中提到, 那么该 section 将被直接拷贝成输出 section。

KEEP(保持)

链接器脚本中的 KEEP 语句将指示链接器保留指定的节,即使其中没有引用任何符号。此语句在链接器脚本的节中使用。当在链接时执行垃圾收集时,这一点就变得很重要,在链接命令行内使用了选项 -gc-sections 后,链接器可能将某些它认为没用的 section 过滤掉,此时就有必要强制让链接器保留一些特定的 section,可用 KEEP() 关键字达此目的。说的通俗易懂就是:

防止被优化。

该语句常见于针对 ARM 体系结构的链接器脚本中,用于将中断向量表放置在偏移量 0x00000000 处。如果没有这个指令,代码中可能不会显式引用的表将被删除。

例如在 NXP imx6ul 相关链接脚本中

.interrupts :
{
    __VECTOR_TABLE = .;
    . = ALIGN(4);
    KEEP(*(.isr_vector))     /* Startup code */
    . = ALIGN(4);
} > m_interrupts

PROVIDE

为在任何链接目标中没有定义但是被引用的一个符号, 而在链接脚本定义一个符号。 PROVIDE(symbol = expression)。下面是一个提供 __bss_start__bss_end 的例子

.bss :
{
    PROVIDE(__bss_start = .);
    *(.bss)
    *(.bss.*)
    *(.dynbss)

    PROVIDE(__bss_end = .);
}

如果程序定义了 __bss_start(带有前导下划线),链接器将给出重复定义错误。

如果程序定义了 bss_start(没有前导下划线),链接器会默认使用程序中的定义。

如果程序引用了 __bss_start 但没有定义它,链接器将使用链接器脚本中的定义。

在 C 语言中可以这样使用

extern char __bss_start;
extern char __bss_end;
#define BSS_START_ADDR    (void *)&__bss_start
#define BSS_END_ADDR    (void *)&__bss_end

char *src = &__bss_start;
char *dst = &__bss_end;

AT(LMA_ADDR)

section 包含两个地址:VMA(virtual memory address 虚拟内存地址或程序地址空间地址) 和 LMA(load memory address 加载内存地址或进程地址空间地址)。默认情况下 LMA 等于 VMA,但可以通过关键字 AT() 指定 LMA。

用关键字 AT() 指定,括号内包含表达式,表达式的值用于设置 LMA。如果不用 AT() 关键字,那么可用 AT>LMA_REGION 表达式设置指定该 section 加载地址的范围。这个属性主要用于构件 ROM 境象。

一般而言, 某 section 的 VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的 flash 中 (由 LMA 指定), 而在运行时将位于 flash 中的输出文件复制到 SDRAM 中 (由 VMA 指定)。

AT(LMA_ADDR) 使用示例

.data : AT(0x00900000)
{
    __data_start__ = .;           /* create a global symbol at data start */
    *(.data)                      /* .data sections */
    *(.data*)                     /* .data* sections */
    . = ALIGN(4);
    __data_end__ = .;             /* define a global symbol at data end */
}

AT>LMA_REGION 使用示例

/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
} >RAM AT> FLASH

ALIGN

表示字节对齐, 如 . = ALIGN(4); 表示从该地址开始后面的存储进行 4 字节对齐。

参考链接

  • http://ftp.gnu.org/
  • http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#IDX274
  • https://blog.csdn.net/Luckiers/article/details/127346876

你可能感兴趣的:(#,qemu-基础篇,section,provide,memory,entry)