【问题标题】:Does the C startup code change addresses of dataC启动代码是否更改数据地址
【发布时间】:2019-10-04 19:00:00
【问题描述】:

在使用 C 代码进行嵌入式开发方面,我了解当程序被编译和链接时,会生成一个在目标上执行的二进制或 ELF 文件。 ELF 文件将包含(以及许多其他内容)全局变量的地址或地址偏移量。

现在,当 C 启动代码首先执行时,如果要在整个程序中修改这些数据,它可以将非常量数据/变量从闪存复制到 RAM。

这会改变系统上变量的内存地址吗?这是否会更新 ELF 文件以更改此数据的地址?

【问题讨论】:

  • embedded ... ELF - 请定义嵌入式。是否嵌入了树莓派? atmega 是嵌入式的吗?您是在询问裸机目标吗?因为它们不“执行 ELF 文件”,所以它们执行机器代码。 if it is to be modified - 我不明白,为什么要修改 C 启动代码? ELF file will contain .. the addresses or address offsets of global variables - 这是一种奇怪的说法。 ELF 文件最重要的是包含标题和机器代码。它不包含“偏移量”,不包含变量,它包含部分。你这是什么意思?
  • @KamilCuk 二进制文件,ELF(例如,如果在 Linux 上使用 Rasberry Pi 等),或者如果用于纯裸机目标,则为普通二进制文件。这些文件包含不同的部分:.text .data。 .bss 等。现在,.data 部分将包含初始化的全局变量,对吧?当 .text 区域中的代码访问这些全局变量时,它会这样做,因为它知道它们的地址。 C 启动代码最初可以将变量从 Flash 复制到 RAM 的事实,我假设这些变量的地址会因此改变?在这种情况下,二进制文件是否会使用新地址进行更新?
  • 地址将如何变化?例如,如果您为 stm32 进行编译,您就会知道 ram 从地址 0x8000000 开始。所以第一个全局变量位于地址 0x8000... 哦,也许是这样。 C 启动代码不会复制“变量”。机器代码中没有“变量”。 C 启动代码将 .data 部分中的所有数据复制到 ram 中,逐字节复制字节。 “变量”一直在 ram 中,只是 crt0 将它们初始化为起始值。变量的初始“值”在 .data 中,而不是“变量”本身。 “变量”一直在内存中(或在寄存器中)。
  • @KamilCuk 只读数据可能会保留在闪存中吗?
  • 好的,所以在二进制文件中,.bss 和 .data 部分中的数据包含变量,它们在复制后将位于 RAM 中的地址?

标签: c linker embedded startup elf


【解决方案1】:

注意:以下描述的是独立(或裸机)嵌入式环境的行为,不一定适用于所有环境。例如,从大容量存储中动态加载代码的托管系统差异很大 - 但即使在这种情况下,这也不是一个有效的答案(即没有“更改”地址)......


没有地址被“更改”。在从 ROM 运行代码的“典型”独立环境中(并非所有嵌入式系统都以这种方式组织,但这可能是您的问题所建议的),链接器将所有符号定位在特定位置。如果这些位置在 RAM 中并具有 initial 值,则 初始化数据 - 不是变量,将存储在 ROM 中并在以下位置复制到 RAM启动 - 变量位置没有改变 - 它们总是在 RAM 中,而初始化数据总是在 ROM 中。

您可以在您的链接器几乎肯定能够生成的链接图中看到这一点。

目标代码中的地址数据 - 无论是 ELF、Intel Hex 还是其他,只有在代码被加载时才有意义 - 原始二进制文件不包含地址信息 - 所有对象都由链接器并由加载器(闪存编程器、调试器或引导加载器)使用其中的地址信息放置在所需的位置。原始二进制文件根本没有地址信息,加载时必须指定加载地址,任何“空白”都必须用填充填充。如果代码不是position independent(即所有地址引用都是相对的),则必须将原始二进制文件加载到正确的特定地址,否则它将无法按预期运行。

当调试器加载 ELF 文件时,它会将二进制文件加载到目标,并将地址和符号信息读取到在主机中运行的调试器中。地址和符号信息是隐含在代码中的,而不是明确地存在于目标中(除非您有基于目标的调试工具可能会使用这些信息)。

C 运行时启动执行以下步骤(至少):

  • 堆栈指针初始化
  • 静态数据的零初始化,无需显式的非零初始化(数据段)
  • 显式初始化的非零静态数据(BSS 段)的初始化
  • 运行时库初始化(如堆初始化)
  • 跳转到主目录。

对于从 RAM 运行代码的系统,还会有一个步骤将代码从 ROM 复制到 RAM(在某些情况下包括图像解压缩),如果运行时支持 C++,则将有一个调用构造函数的步骤所有静态对象。此运行时启动代码通常由您的工具链提供,您可能永远不会注意到它,但几乎可以肯定它会作为源代码(汇编程序和/或 C 语言)提供给您,供您自定义或看看它是如何工作的。

数据和 BSS 段通常是单个连续块,零初始化只是块内存写入零,数据初始化只是块从 ROM 到 RAM 的内存复制(可能带有解压缩)。

代码段 - 无论是在 ROM 还是 RAM 中都称为 text 段。

所以你有两个内存映射 - 包含文本、常量数据、初始化数据和引导或启动代码的映像内存映射(可 ROM),以及包含文本、常量数据、数据的运行时内存映射、BSS、堆栈和堆段。数据、BSS、堆栈和堆必须在 RAM 中,文本和常量数据可能在 ROM 或 RAM 中,具体取决于系统的运行时架构。

参考:https://en.wikipedia.org/wiki/Data_segment

【讨论】:

  • 感谢您的详细回复。您声明“原始二进制文件不包含地址信息”。如果代码是位置相关的,那么它必须包含地址,对吗?如果代码是位置无关的,那么代码中是否会有相对偏移量从哪里获取数据(例如全局变量)?
  • @Engineer999 :它不包含地址信息,因为它不包含加载程序用来定位代码的元数据。显然,它包含带有跳转/分支指令到特定地址的代码——这不是“信息”。不要挂断与位置无关的代码,我只是顺便提到它,它几乎不相关 - 只是作为我断言的警告,您需要在加载二进制图像时指定它的起始地址。 en.wikipedia.org/wiki/Position-independent_code
【解决方案2】:

现在,当 C 启动代码首先执行时,它可以复制非常量 数据/变量从闪存到 RAM,如果该数据是 在整个程序中进行修改。

这将改变变量的内存地址 系统?这是否会更新 ELF 文件以更改 这个数据?

在整个程序代码中对变量的所有引用都将使用 RAM、.bss.data 部分中的地址。用于初始化的 ROM 常量,存储在.rodata 中,将有一些其他地址,只有启动代码(“CRT”)关心。无论是 ELF 还是最终的二进制文件,这都是一样的。

有关详细信息,请参阅 EE 网站上的此帖子:What resides in the different memory types of a microcontroller?

【讨论】:

    猜你喜欢
    • 2013-03-16
    • 2011-07-29
    • 2013-02-13
    • 2018-05-31
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 2014-05-06
    相关资源
    最近更新 更多