gcc - AARCH64 上的 memcpy 产生未对齐的数据中止异常、ARM GNU 工具链或 newlibc Bug?

标签 gcc arm arm64 memcpy newlib

我已经在一个裸机项目中使用 ARM GCC 版本 aarch64-none-elf-gcc-11.2.1 一段时间了,在一个大型项目中,该项目已成功使用 libc 函数(malloc/memcpy)多次,没有出现问题这些选项:

-L$AARCH64_GCC_PATH/aarch64-none-elf/lib -lc -lnosys -lg

尽管使用 -mstrict-align 进行编译,但我最近发现由于 memcpy 期间访问未对齐而导致的异常。

在隔离问题并创建单元测试后,我相信我发现了一个错误,请忽略 objdump 和 memcpy 调用中的地址,只是将它们用于此测试。

//unit test
#include <stdlib.h>
#include <string.h>
volatile int bssTest;

void swap(int a, int b) {
    memcpy((void*)0x500,(void*)0x1000,0xc);
}
0000000000060040 <memcpy>:
   60040:   f9800020    prfm    pldl1keep, [x1]
   60044:   8b020024    add x4, x1, x2
   60048:   8b020005    add x5, x0, x2
   6004c:   f100405f    cmp x2, #0x10
   60050:   54000209    b.ls    60090 <memcpy+0x50>  // b.plast
   60054:   f101805f    cmp x2, #0x60
   60058:   54000648    b.hi    60120 <memcpy+0xe0>  // b.pmore
   6005c:   d1000449    sub x9, x2, #0x1
   60060:   a9401c26    ldp x6, x7, [x1]
   60064:   37300469    tbnz    w9, #6, 600f0 <memcpy+0xb0>
   60068:   a97f348c    ldp x12, x13, [x4, #-16]
   6006c:   362800a9    tbz w9, #5, 60080 <memcpy+0x40>
   60070:   a9412428    ldp x8, x9, [x1, #16]
   60074:   a97e2c8a    ldp x10, x11, [x4, #-32]
   60078:   a9012408    stp x8, x9, [x0, #16]
   6007c:   a93e2caa    stp x10, x11, [x5, #-32]
   60080:   a9001c06    stp x6, x7, [x0]
   60084:   a93f34ac    stp x12, x13, [x5, #-16]
   60088:   d65f03c0    ret
   6008c:   d503201f    nop
   60090:   f100205f    cmp x2, #0x8
   60094:   540000e3    b.cc    600b0 <memcpy+0x70>  // b.lo, b.ul, b.last
   60098:   f9400026    ldr x6, [x1]
   6009c:   f85f8087    ldur    x7, [x4, #-8]
   600a0:   f9000006    str x6, [x0]
   600a4:   f81f80a7    stur    x7, [x5, #-8]
   600a8:   d65f03c0    ret
   600ac:   d503201f    nop
   600b0:   361000c2    tbz w2, #2, 600c8 <memcpy+0x88>
   600b4:   b9400026    ldr w6, [x1]
   600b8:   b85fc087    ldur    w7, [x4, #-4]
   600bc:   b9000006    str w6, [x0]
   600c0:   b81fc0a7    stur    w7, [x5, #-4]
   600c4:   d65f03c0    ret
   600c8:   b4000102    cbz x2, 600e8 <memcpy+0xa8>
   600cc:   d341fc49    lsr x9, x2, #1
   600d0:   39400026    ldrb    w6, [x1]
   600d4:   385ff087    ldurb   w7, [x4, #-1]
   600d8:   38696828    ldrb    w8, [x1, x9]
   600dc:   39000006    strb    w6, [x0]
   600e0:   38296808    strb    w8, [x0, x9]
   600e4:   381ff0a7    sturb   w7, [x5, #-1]
   600e8:   d65f03c0    ret
   600ec:   d503201f    nop
   600f0:   a9412428    ldp x8, x9, [x1, #16]
   600f4:   a9422c2a    ldp x10, x11, [x1, #32]
   600f8:   a943342c    ldp x12, x13, [x1, #48]
   600fc:   a97e0881    ldp x1, x2, [x4, #-32]
   60100:   a97f0c84    ldp x4, x3, [x4, #-16]
   60104:   a9001c06    stp x6, x7, [x0]
   60108:   a9012408    stp x8, x9, [x0, #16]
   6010c:   a9022c0a    stp x10, x11, [x0, #32]
   60110:   a903340c    stp x12, x13, [x0, #48]
   60114:   a93e08a1    stp x1, x2, [x5, #-32]
   60118:   a93f0ca4    stp x4, x3, [x5, #-16]
   6011c:   d65f03c0    ret
   60120:   92400c09    and x9, x0, #0xf
   60124:   927cec03    and x3, x0, #0xfffffffffffffff0
   60128:   a940342c    ldp x12, x13, [x1]
   6012c:   cb090021    sub x1, x1, x9
   60130:   8b090042    add x2, x2, x9
   60134:   a9411c26    ldp x6, x7, [x1, #16]
   60138:   a900340c    stp x12, x13, [x0]
   6013c:   a9422428    ldp x8, x9, [x1, #32]
   60140:   a9432c2a    ldp x10, x11, [x1, #48]
   60144:   a9c4342c    ldp x12, x13, [x1, #64]!
   60148:   f1024042    subs    x2, x2, #0x90
   6014c:   54000169    b.ls    60178 <memcpy+0x138>  // b.plast
   60150:   a9011c66    stp x6, x7, [x3, #16]
   60154:   a9411c26    ldp x6, x7, [x1, #16]
   60158:   a9022468    stp x8, x9, [x3, #32]
   6015c:   a9422428    ldp x8, x9, [x1, #32]
   60160:   a9032c6a    stp x10, x11, [x3, #48]
   60164:   a9432c2a    ldp x10, x11, [x1, #48]
   60168:   a984346c    stp x12, x13, [x3, #64]!
   6016c:   a9c4342c    ldp x12, x13, [x1, #64]!
   60170:   f1010042    subs    x2, x2, #0x40
   60174:   54fffee8    b.hi    60150 <memcpy+0x110>  // b.pmore
   60178:   a97c0881    ldp x1, x2, [x4, #-64]
   6017c:   a9011c66    stp x6, x7, [x3, #16]
   60180:   a97d1c86    ldp x6, x7, [x4, #-48]
   60184:   a9022468    stp x8, x9, [x3, #32]
   60188:   a97e2488    ldp x8, x9, [x4, #-32]
   6018c:   a9032c6a    stp x10, x11, [x3, #48]
   60190:   a97f2c8a    ldp x10, x11, [x4, #-16]
   60194:   a904346c    stp x12, x13, [x3, #64]
   60198:   a93c08a1    stp x1, x2, [x5, #-64]
   6019c:   a93d1ca6    stp x6, x7, [x5, #-48]
   601a0:   a93e24a8    stp x8, x9, [x5, #-32]
   601a4:   a93f2caa    stp x10, x11, [x5, #-16]
   601a8:   d65f03c0    ret
   601ac:   00000000    udf #0

当在大小 = 0x8 + 0x4n 的设备类型内存上执行 memcpy 时,其中 n 是任何自然数,即使注意对齐 src/dst 指针,也会抛出异常,该指令从 aarch64 上 memcpy 的以下 objdump 在 6009c 上看到,导致 ldur x7, [x4, #-8]。在大小为 0xc 的情况下,复制会将以 0x4 结尾的 32 位对齐地址执行 LDUR 到 64 位 x 寄存器,这会导致系统类型内存上的数据中止。

虽然我知道在裸机应用程序中使用 stdlib 函数时必须小心,但由于我们代码库的性质,很难确保每次调用 memcpy 的大小都是 64 位对齐的。 newlib/compiler 不应该注意确保 memcpy 将为任何 32 位对齐的 memcpy 使用 32 位 w 寄存器吗?特别是使用 -mstrict-align 时?

就同时提供立即修复而言,我有什么选择,我想我可以尝试覆盖 memcpy 的定义,但在这种情况下我应该基于什么来源来替换实现。

如果有任何帮助,我们将不胜感激,谢谢。

最佳答案

其实,我觉得更大的“bug”就在你的预料之中。您根本无法在设备内存上使用 memcpy 或任何其他库函数。

现代优化编译器和库的默认假设是它们在普通内存上运行,其访问没有副作用,并且不会被任何其他软件或硬件同时访问(*)。因此,未对齐的访问(gcc 和 newlib 默认情况下认为是可以的)是您最不用担心的。对于 memcpy 来说,使用任何加载或存储组合来完成其工作是完全公平的游戏。包括:

  • 三个 4 字节访问

  • 8 字节和 4 字节访问

  • 十二次一字节访问

  • 两个重叠的八字节访问

  • 超出源缓冲区边界的16字节加载,如果可以证明不会跨越页面边界

  • 同一地址的多次加载

  • 多次存储到同一地址,其中除最后一个之外的任何一个都可能是错误的值

使用-mstrict-align并没有真正的帮助。首先,正如您已经注意到的,它只影响您实际用它编译的代码;它对已经构建的库代码没有任何作用。您必须使用此选项重建所有 newlib,然后单独审核 newlib 中的所有汇编代码。但它对解决上述任何其他问题都没有帮助,所有这些问题都可能对设备内存造成灾难性的影响。 (正如 amonakov 指出的,由于 -mstrict-align 很少使用,因此很容易出现编译器错误。)

对于设备内存,您需要精确控制加载和存储的数量、地址、大小以及顺序。 C/C++ 中只有一种机制可以实现这一点,即 volatile 。因此,对设备内存的所有访问都需要通过 volatile 指针或使用汇编来显式完成。

如果您需要完成 32 位访问,我认为编写示例代码的唯一安全方法是:

volatile uint32_t *dest = (volatile uint32_t *)0x500;
volatile uint32_t *src = (volatile uint32_t *)0x1000;
for (int i = 0; i < 3; i++)
    dest[i] = src[i];

如果您对所有设备内存执行此操作,那么您可以在普通内存上安全地使用编译后的代码和库函数,而无需 -mstrict-align 。 (前提是您在页表中正确标记了所有正常内存,并且 SCTLR_ELx.A 位已清除。)


(*) C/C++ 数据竞争规则确实允许多个读取器同时访问同一内存。所以你可以假设你没有显式写入的内 stub 本不会被写入。除此之外,编译器几乎可以完全自由地以任何方式发明/丢弃/组合/重新排序加载和存储。

关于gcc - AARCH64 上的 memcpy 产生未对齐的数据中止异常、ARM GNU 工具链或 newlibc Bug?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76270191/

相关文章:

c - 检索库运行时

c - 如何衡量ARM性能?

c - 如何在 ARM 上使用 kgdb?

c - 在 GCC 中不能除数的原因是什么

android - 应该使用哪个 Android 虚拟设备在 Windows 上启动 arm64-v8a APK?

linux-device-driver - QEMU 中关于 virtio-net-pci 和 virtio-net 的混淆

windows - 在调用 asm 函数之前在 C 中调用 printf 或不调用的神秘副作用?

c++ - wrong-looking编译错误调用模板类的模板成员函数

C 代码在 archlinux 上运行,但不能在 ubuntu 10.04 上运行

gcc - 创建 aarch64 裸机程序时如何防止 "main.o:(.eh_frame+0x1c): relocation truncated to fit: R_AARCH64_PREL32 against ` .text'"?