将 memcpy 移动到另一个代码部分

Los*_*les 5 c linker gcc arm linker-scripts

我正在构建一个旨在在 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;
Run Code Online (Sandbox Code Playgroud)

_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);
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

在我的芯片架构中,地址0x20000000直到0xE000000大约位于 SRAM 中(我的设备上实际只有 4Kb)。下面的任何地址0x1fffffc00都位于闪存部分。

问题是这样的:在我的.bootloader部分 ( ) 中的函数中,插入了对( 、和)bootloader_usb_endp0_handler的引用,因为我正在执行结构复制等操作。它所指向的引用位于地址,该地址位于闪存中......可以被擦除。memcpy2000039c200005622000056cmemcpy0x00000869

具体代码为:

static setup_t last_setup;
last_setup = *((setup_t*)(bdt->addr));
Run Code Online (Sandbox Code Playgroud)

其中setup_t是一个两个字的结构体,并且bdt->addr是 avoid*我知道它指向看起来像 a 的数据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
Run Code Online (Sandbox Code Playgroud)

通常优化是-Os,但我试图摆脱memcpy......它不起作用。

另外,我看过这个问题,但没有解决问题。

mfr*_*fro 0

我从未尝试过,但您可能会使用EXTERN()链接器脚本指令强制加载 newlibmemcpy()两次 - 首先在引导加载程序链接阶段进入您所需的部分,然后取消定义它并将其第二次链接到您的“正常”代码中。