我正在学习 ARM 上的裸机编程如何工作,但我很难理解如何使用链接器脚本中定义的地址。
这是我的链接器脚本:
ENTRY(ResetHandler)
MEMORY
{
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text :
{
KEEP(*(.isr_vector))
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
. = ALIGN(4);
_etext = .;
}>ROM AT>ROM
.data :
{
_sdata = .;
*(.data)
*(.data.*)
. = ALIGN(4);
_edata = .;
}>RAM AT>ROM
.bss (NOLOAD) :
{
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
}>RAM AT>ROM
}
所有节的地址都在链接器文件中描述。我不明白的是,我的最终编译结果是一个原始二进制文件,仅包含代码和数据,没有地址。加载此二进制文件时,当我在加载过程中除了二进制文件之外没有指定任何内容时,如何将这些节定位到链接器文件中定义的正确地址?链接描述文件中有关 LMA 和 VMA 的所有信息都会丢失。这一切都是由引导加载程序执行的吗?
所以它的工作原理是......不,没有引导加载程序,你必须符合硬件/逻辑。要使用 elf 版本的二进制文件(它们都被视为二进制文件),您需要一些软件来解析该文件,就像您在操作系统上的命令或 gui 上运行程序一样。
从这个开始
用于演示目的的最小限度的东西。您可以反汇编该对象以查看我们在二进制文件中寻找哪种数据。
这些地址都是从零开始的,因为尚未链接。
从这个链接描述文件开始
链接给出
我没有使用真实地址,不需要填满我的硬盘......这很容易演示它是如何工作的。
要为 MCU 上的闪存准备数据,请使用 objcopy 和 -O 二进制文件。它的作用是查看 elf 文件的可加载部分,并从最低地址开始,然后填充文件,以便其他部分落在正确的位置。如果您愿意的话,这种形式的二进制文件是内存映像,但基地址或任何其他信息都不在文件格式中。用户必须知道。
因此,这向我们展示了这个二进制文件以我们在最低地址 0x1000 处定义的字节开始,然后填充文件,以便我们在 0x2000 处想要的字节是文件中的 0x1000 字节。该文件大小为 0x1004 字节。
您的 MCU 没有 0x20000000-0x08000000+加上设备中闪存的数据量,如果它是闪存,那么您的代码将无法工作。您的读写 sram 位于 0x20000000,闪存位于 0x08000000,并且有一定大小。
因此,必须首先根据最终地址定义链接所有内容,然后将所有数据打包到闪存中。
AT >rom(无论如何,由于某种原因必须大写)执行此操作。它说我希望您链接 ram 地址空间定义的 .data,但我希望您根据 rom 地址空间定义加载它。
现在我们得到了这个
在你开始执行编译代码之前,在你的 C 入口点(通常是 main() 之前),由程序员来获取 .data 并将其复制到 ram,但是使用 main 这个词可能/确实会导致你的程序中出现额外的、未使用的东西。二进制文件消耗宝贵的资源,您不必使用 main,任何有效的函数名称都可以)
你怎么知道有多少以及在哪里?让工具为您完成工作。
和
这已经相当丑陋了(为什么我不在我的代码中使用 .data 也不期望 .bss 为零,这使得一切都变得漂亮并且工作得很好)。
和
我的so.bin文件是36字节(注意我改成了真实的stm32f103地址)。
和
您可以在链接描述文件中创建可在代码中使用/查看的标签/地址(1等)。这些工具会为您完成工作。
8000010: 08000020 .字一 8000014: 20000000 .字二 8000018: 20000004 .字三 800001c: 20000004 .字四
因此,您可以采用三减二来获取要复制的字节数,或者您可以让循环从二开始,而从一复制时则小于三。也许是这样的
(如果两端对齐 8,则可以执行两个单词的 stm,或 16 个单词的 stm,从而使复制速度更快)。
是的,这是正确的
它是复位向量的地址 ORRED(不认为是 add,而是认为 orr)加 1。lsbit 必须设置为 0x08000008|1 = 0x08000009。在像我上面所做的那样构建新设置时执行二进制文件之前,在尝试运行它之前,请检查向量表。如果您没有正确完成向量表,并且根据您的电路板布局和芯片,它可能会崩溃,这可能是一块砖块板。或者至少需要更多的电线或焊料来将其拆开。
这个 lsbit 的东西在 arm 文档中,在开始任何这项工作之前你应该拥有它。相同的文档表示一个入口点,但从技术上讲,如果您遵循它,arm 不会将入口点记录为 0x00000000(默认 VTOR),它实际上是由芯片供应商逻辑内的 Arm 逻辑边缘的信号/带定义的(在本例中为 st) ),并且它们不必使用 0x00000000。但如果他们不这样做,他们可能会生气的顾客......但是等等,他们没有做对吗?0x08000000 不是 0x00000000。实际情况并非如此。在 st 文档中搜索 boot0,在执行任何此工作之前也需要该文档(参考手册,而不是程序员手册,从 st 中,您需要数据表和参考手册,如果它们的板像 nucleo 那么用户该委员会的手册,一般来说没有来自圣的其他文档。
您会发现 st 文档说复位时的 boot0 和 boot1 引脚组合将确定 0x00000000 的别名地址。对于正常操作,它指向位于 0x08000000 的应用程序闪存。因此逻辑读取 0x00000000 和 0x00000004 st 别名到 0x08000000 并找到
将 0x20000000 写入堆栈指针,然后从 0x08000008 获取其第一条指令。通过链接到 0x08000000 而不是 0x00000000(这在技术上可以在某些板上工作),您现在从一开始就没有别名,您位于正确的地址空间中。别名窗口的大小不像某些部件上的某些闪烁那么大,因此您会遇到问题。一些较新的 nucleo 板调试器固件将不再加载二进制文件,如果它看到第二个字是从零开始的,它将声明一个错误。
例如,选择另一个 boot0/boot1 组合将引导内部工厂引导加载程序,在这部分,您可以使用 uart 及其 uart 协议对闪存进行编程,然后更改 boot0/boot1 输入,然后重置以引导到你的固件中。您还可以选择从 sram 启动的 boot0/boot1 组合,因此您将程序链接并放置在 0x20000000(带有向量表),然后通过 swd 调试器将程序下载到 sram,然后进行重置,它将从以下位置启动您的程序:萨拉姆。是的,当然你可以将程序加载到 sram 中并从调试器启动它,而不需要向量表等,但他们选择添加此功能。他们的一些部件带有 USB,当选择内部调试器时能够通过 USB 下载,而 F103 部件具有 USB,它没有这个功能。您看到蓝色药丸具有 USB 下载功能的地方,是因为它在应用程序闪存中有一个引导加载程序,如果您使用基于 arduino 沙箱和蓝色药丸的基础设施,您的应用程序将包含此引导加载程序。一些蓝色药丸发货时没有正确的装载机,您必须使用 uart 或 SWD。有些是这样做的,你可以直接使用 arduino 沙箱。不确定您是否有蓝色药丸,以及您目前是否正在使用沙盒,或者因为您想离开沙盒而问这个问题 有些是这样做的,你可以直接使用 arduino 沙箱。不确定您是否有蓝色药丸,以及您目前是否正在使用沙盒,或者因为您想离开沙盒而问这个问题 有些是这样做的,你可以直接使用 arduino 沙箱。不确定您是否有蓝色药丸,以及您目前是否正在使用沙盒,或者因为您想离开沙盒而问这个问题
简短的回答。是的,-O 二进制文件的所有地址信息都会丢失。但那很好。根据逻辑规则(前面的向量表),正确构建的二进制文件是一个用于闪存地址空间的块。根据该芯片的硬件规格,该图像加载/存在于从 0x08000000 开始的地址空间中,因此二进制文件中的第一个字是 0x08000000 处的第一个字。与引导程序(不可分割的一对)结合的链接器脚本承担了工具链为您所做的工作,以便您的引导程序可以在调用入口点之前将 .data 从闪存复制到 ram 并将 .bss 归零。
如果您正在为操作系统制作二进制文件,则该操作系统有关于该二进制文件的构造的规则,您需要遵守该规则(linux 和 windows 的 hello_world.c 不是相同的规则,我也不假设 linux 和BSD)。对于 MCU,您必须遵守特定的芯片规则(请注意,到目前为止,stm32 中都支持 0x08000000,但有些您想使用 0x00200000 而不是 0x08000000。例如基于 cortex-m7 的规则)
您的链接器脚本“不需要”入口点,因为没有操作系统来观察该入口点,程序员必须与链接器一起放置“入口”点,该“入口”点从硬件中是向量表而不是重置向量。现在说,如果您使用链接时间优化从二进制文件中删除未使用的代码/数据(对于资源受限的单片机工作来说不是一个坏主意),那么链接时间优化通过遵循代码路径来工作,并且需要一个起点,它如果您没有 ENTRY,它甚至可能不会抱怨,它会创建一个空的二进制文件(您的 objdump 看起来不错,但 -O 二进制文件将为空或会失败)。如果你让链接器显示它删除的内容(添加它是个好主意,当选择 lto 时它应该是默认的)然后你会看到所有内容都被删除,包括你的重置向量,让你意识到你做错了什么。如果你想在 qemu arm 上运行你的 elf 文件,那么我们在这里学到的是,你还需要一个入口点,但不确定它是重置向量还是向量表,基本上它需要是一个地址或红色用一个告诉 qemu 这是一个arm条目还是cortex-m(即使你在命令行上指定了它!!!)。这就是他们的设计方式...叹息...否则你不需要入口点。基本上它需要是一个地址或加1来告诉qemu这是一个arm条目还是cortex-m(即使你在命令行上指定了它!!!)。这就是他们的设计方式...叹息...否则你不需要入口点。基本上它需要是一个地址或加1来告诉qemu这是一个arm条目还是cortex-m(即使你在命令行上指定了它!!!)。这就是他们的设计方式...叹息...否则你不需要入口点。
另外顺便说一句,您不需要 _start (并且已经提到您不需要 main())。如果您不提供自己的链接器脚本,则 _start 和 main 来自默认链接器脚本,与您的工具链(和/或 C 库)关联的默认链接器脚本具有 ENTRY(_start),您可以轻松地 grep 。与此链接器脚本一起使用的 C 库引导程序会调用 main(),无论如何您都不想使用工具链引导程序。你经常看到 _start 因为人们不明白它为什么在那里。即使不使用,我也会这样做,这更像是一种习惯。你可以在我上面的输出中看到
在那里使用 _start 或某个标签会给我们一个表格标签,而不是与附近其他标签相关的数学标签,因此 _start 或其他任何可以使阅读更清晰的内容。
您不需要 isr_vector 或向量表的任何特殊部分,只需对这些工具有基本的了解即可。我的项目的 stm32 链接器脚本如下所示
我使用全局变量(当然,这是一个资源受限的嵌入式平台!)并且一切都很好。没有酥油奇特...嗯..东西。我发现 (rwx) 的东西产生的问题比解决方案更多,所以我非常避免这样做。
IMO少即是多。大多数人喜欢在一个疯狂的(注意:特定于工具链的)链接器脚本中解决他们所有的世俗问题,这是微妙而复杂的。我永远不会理解那种心态。
该死,这应该是简短的答案,怎么样:
The addressing is lost, yes this is correct. A properly built binary is meant to load into the application flash on the part and be prepared to work with the chip logic. (vector table up front basically and linked properly). The -O binary output of objcopy starts with the lowest loadable address defined, 0x08000000 in your case and then padded as necessary (properly built would not have padding other than whatever alignment the tool did or you asked for) to line everything up.
So the addressing information from the elf file is lost. But both the programmer and the logic know the rules for that platform. And the .data and .bss knowledge are put in the image/code by the programmer, ideally with the assistance of the toolchain. Basically the other address spaces ARE in the file, but as part of the code not as part of a binary file format.
(even if you take someone elses code or you play in someones sandbox, you are still the programmer responsible for making sure the binary is generated properly for this target)