【发布时间】:2015-03-22 04:23:55
【问题描述】:
我正在构建一个旨在在 ARM Cortex-M0+ 微控制器上运行的软件。它包括一个 USB 引导加载程序,在调用函数时作为辅助程序运行。我在编译期间插入 memcpy 函数时遇到问题。
背景
链接描述文件是一切开始的地方。其中大部分都非常简单和标准。该程序存储在.text 并从那里执行。 .text 中的所有内容都存储在芯片的闪存部分。
奇怪的是引导加载程序运行的部分。为了能够在不覆盖引导加载程序代码的情况下写入所有闪存,我的引导加载程序入口点将引导加载程序程序的副本启动到微控制器的 SRAM 部分,然后从那里执行它。这样,引导加载程序可以安全地擦除设备上的所有闪存,而不会无意中删除自身。
这是通过在链接描述文件中做一个伪造的“覆盖”来实现的(真正的OVERLAY 与我的用例不太匹配):
/**
* The bootloader and general ram live in the same area of memory
* NOTE: The bootloader gets its own special RAM space and it lives on top
* of both .data and .bss.
*/
_shared_start = .;
.bootloader _shared_start : AT(_end_flash)
{
/* We keep the bootloader and its data together */
_start_bootloader_flash = LOADADDR(.bootloader);
_start_bootloader = .;
*(.bootloader.data)
*(.bootloader.data.*)
. = ALIGN(1024); /* Interrupt vector tables must be aligned to a 1024-byte boundary */
*(.bootloader.interrupt_vector_table)
*(.bootloader)
_end_bootloader = .;
}
.data _shared_start : AT(_end_flash + SIZEOF(.bootloader))
{
_start_data_flash = LOADADDR(.data);
_start_data = .;
*(.data)
*(.data.*)
*(.shdata)
_end_data = .;
}
. = _shared_start + SIZEOF (.data);
_bootloader_size = _end_bootloader - _start_bootloader;
_data_size = _end_data - _start_data;
_end_flash 是对上一节末尾的引用,该节将其所有数据存储在闪存中(.text、.rodata、.init...基本上所有只读文件都会卡在那里)。
这样做的结果是 .data 和 .bss 部分通常位于 RAM 中。但是,.bootloader 部分也位于 RAM 中的相同位置。编译时,这两个部分都按顺序存储到闪存中。在我的crt0 例程中,.data 部分从闪存复制到 RAM 中的相应地址(由_start_data 指定),.bss 部分被归零。我在.text 部分中存储了一个附加部分,它通过将其数据从闪存复制到RAM 来启动引导加载程序,覆盖.data 和.bss 中的任何内容。引导加载程序的唯一退出是系统重置,因此它可以破坏正在运行的程序的数据。将引导加载程序复制到 RAM 后,它会执行它。
问题
显然,在编译覆盖程序并确保所有引用都对齐时可能会出现一些问题。为了缓解从普通程序访问引导加载程序代码或从引导加载程序访问普通.data 或.bss 的问题,我的链接器脚本中有以下三行:
NOCROSSREFS(.bootloader .text);
NOCROSSREFS(.bootloader .data);
NOCROSSREFS(.bootloader .bss);
现在,每当我在.text(可能会被引导加载程序删除)、.data(引导加载程序位于其之上)或.bss(同样,引导加载程序位于顶部)之间存在交叉时) 和.bootloader 部分,将发出编译器错误。
在我真正开始编写代码之前,这非常有效。我的部分代码包括一些结构复制和其他类似的东西。显然,编译器决定这样做(bootloader_ 函数位于 .bootloader 部分):
20000340 <bootloader_usb_endp0_handler>:
...
20000398: 1c11 adds r1, r2, #0
2000039a: 1c1a adds r2, r3, #0
2000039c: f000 f8e0 bl 20000560 <__memcpy_veneer>
...
20000560 <__memcpy_veneer>:
20000560: b401 push {r0}
20000562: 4802 ldr r0, [pc, #8] ; (2000056c <__memcpy_veneer+0xc>)
20000564: 4684 mov ip, r0
20000566: bc01 pop {r0}
20000568: 4760 bx ip
2000056a: bf00 nop
2000056c: 00000869 andeq r0, r0, r9, ror #16
在我的芯片架构中,地址 0x20000000 到 0xE000000 左右位于 SRAM 中(设备上实际只有 4Kb)。 0x1fffffc00 以下的任何地址都位于闪存部分。
问题是这样的:在我位于 .bootloader 部分 (bootloader_usb_endp0_handler) 的函数中,插入了对 memcpy(2000039c、20000562 和 2000056c)的引用,因为我是做一个结构复制等等。它对memcpy 的引用位于地址0x00000869,它存在于闪存中……可以被擦除。
具体代码为:
static setup_t last_setup;
last_setup = *((setup_t*)(bdt->addr));
其中setup_t 是一个双字结构,bdt->addr 是一个void*,我知道它指向看起来像setup_t 的数据。此行生成对memcpy 的调用。
我的问题是:我真的很想保留我的结构复制。这很方便。有没有办法指定编译器将 memcpy 放入默认部分以外的特定部分?我希望这种情况发生只是为了引导加载程序模块。所有其他代码都可以是memcpy...我只想要一个位于.bootloader 中的引导加载程序模块的特殊副本。
如果这根本不可能,我要么用汇编语言编写整个引导加载程序(不那么有趣),要么单独编译引导加载程序,在最终程序中将它作为一个相当长的十六进制字符串包括在内,并在将字符串复制到 RAM 后执行该字符串。字符串路由对我的吸引力不是很好,因为它易碎且难以实现......所以任何其他建议也将不胜感激。
这个模块的编译行是:
arm-none-eabi-gcc -Wall -fno-common -mthumb -mcpu=cortex-m0plus -ffreestanding -fno-builtin -nodefaultlibs -nostdlib -O0 -c src/bootloader.c -o obj/bootloader.o
通常优化是-Os,但我试图摆脱memcpy...它没有用。
另外,我查看了this question 并没有解决问题。
【问题讨论】:
-
把memcpy写成宏?
-
我实际上并没有调用
memcpy...gcc 在我进行结构复制时会插入它,所以我不确定将它写成宏会有所帮助。我用罪魁祸首的代码行编辑了我的帖子。 -
对不起,我错过了那部分。那么你提供 memcpy 实现吗? gcc 仍然插入带有 -fno-builtin 标志的 memcpy 操作不是很奇怪吗?
-
arm-none-eabi-gcc似乎有一些 memcpy 实现,它插入到我的代码中。-fno-builtin仍然创建引用确实很奇怪。 -
我想知道这是否可能被视为 gcc 上的错误。您使用的是哪个版本?如果添加自己的 memcpy 实现会怎样?
标签: c gcc linker arm linker-scripts