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

问题描述 投票:0回答:1

我在裸机项目中使用 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 指针,6009c 上的指令来自以下 aarch64 上 memcpy 的 objdump 导致 ldur x7,[x4,#-8]。在大小为 0xc 的情况下,副本将对以 0x4 结尾的 32 位对齐地址执行 LDUR 到 64 位 x 寄存器,这会导致系统类型内存上的数据中止。

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

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

感谢任何帮助,谢谢。

gcc arm arm64 memcpy newlib
1个回答
0
投票

其实我觉得更大的“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++ 数据竞争规则确实允许多个 reader 同时访问同一内存。所以你可以假设你没有明确写入的内存根本不会被写入。除此之外,编译器几乎可以完全自由地以任何方式发明/丢弃/组合/重新排序加载和存储。

© www.soinside.com 2019 - 2024. All rights reserved.