ARM 交叉编译器在标准 C 函数中生成无效的分支参数

ARM cross compiler generating invalid branch argets in standard C functions

我正在开发一个自定义嵌入式项目(使用 PlatformIO 设置构建环境),我发现调用标准 C 函数(如 memset、memcpy)会生成伪代码。反汇编表明,这两个函数(以及我从 stdlib 尝试过的其他函数)中的指令无条件地分支到不包含代码的位置,这当然会导致 MCU(Cortex M4、Atmel D51)在尝试时出现硬故障执行无意义的代码。没有编译器错误,只有由于无效指令导致的硬故障形式的运行时错误。

我认为我的编译环境有问题,因为 PlatformIO 有一些库用于同一处理器的 Adafruit 板,并且正确链接了上述功能。请注意,我是从 Mac 交叉编译的。下面是 Adafruit 和自定义项目中 memset 函数的反汇编:

Adafruit:

0x000012de: 02 44               add r2, r0
0x000012e0: 03 46               mov r3, r0
0x000012e2: 93 42               cmp r3, r2
0x000012e4: 00 d1               bne.n   0x12e8 <memset+10>
0x000012e6: 70 47               bx  lr
0x000012e8: 03 f8 01 1b         strb.w  r1, [r3], #1
0x000012ec: f9 e7               b.n 0x12e2 <memset+4>

自定义:

0x000005b4: 00 30               adds    r0, #0
0x000005b6: a0 e1               b.n 0x8fa           <--- branch to address with no code and hard-fault
0x000005b8: 02 20               movs    r0, #2
0x000005ba: 80 e0               b.n 0x6be
0x000005bc: 02 00               movs    r2, r0
0x000005be: 53 e1               b.n 0x868
0x000005c0: 1e ff 2f 01         vrhadd.u16  d0, d14, d31
0x000005c4: 01 10               asrs    r1, r0, #32
0x000005c6: c3 e4               b.n 0xffffff50
0x000005c8: fb ff ff ea                 ; <UNDEFINED> instruction: 0xfffbeaff

即使没有无意义的分支目标,自定义版本的形式也与上面的完全不同,向我暗示链接发生了可怕的错误。我认为问题出在链接阶段,而不是在编译单个目标文件期间。仅存在于我的项目中的文件之间的链接没有问题;本地分支是正确的。这种怪异似乎仅限于链接预建库。

我应该提一下,adafruit 的东西还包括 Arduino 代码,所以编译过程的一部分包括 C++,而我的是纯 C。我已经将大部分编译器标志和构建环境基于 Adafruit 项目,因为它是我自己项目的最佳参考,但我没有以任何形式使用 arduino。

下面是两个项目分别调用链接器的方式

Adafruit(g++ 可以与 gcc 互换,没有错误):

arm-none-eabi-g++ -o .pio/build/adafruit_grandcentral_m4/firmware.elf -T flash_without_bootloader.ld -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Os -mcpu=cortex-m4 -mthumb -Wl,--gc-sections -Wl,--check-sections -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align --specs=nosys.specs --specs=nano.specs .pio/build/adafruit_grandcentral_m4/src/main.cpp.o -L.pio/build/adafruit_grandcentral_m4 -L/Users/work-reese/.platformio/packages/framework-arduino-samd-adafruit/variants/grand_central_m4/linker_scripts/gcc -L/Users/work-reese/.platformio/packages/framework-cmsis/CMSIS/Lib/GCC -Wl,--start-group .pio/build/adafruit_grandcentral_m4/libFrameworkArduinoVariant.a .pio/build/adafruit_grandcentral_m4/libFrameworkArduino.a -larm_cortexM4lf_math -lm -Wl,--end-group

自定义:

arm-none-eabi-ar rc .pio/build/commonsense/libFrameworkCommonSense.a .pio/build/commonsense/FrameworkCommonSense/commonsense.o .pio/build/commonsense/FrameworkCommonSense/cortex_handlers.o .pio/build/commonsense/FrameworkCommonSense/led.o .pio/build/commonsense/FrameworkCommonSense/pinConfig.o .pio/build/commonsense/FrameworkCommonSense/startup.o

arm-none-eabi-ranlib .pio/build/commonsense/libFrameworkCommonSense.a

arm-none-eabi-gcc -o .pio/build/commonsense/firmware.elf -T commonsense_linker.ld -mfpu=fpv4-sp-d16 -mthumb -Wl,--gc-sections -Wl,--check-sections -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--warn-section-align --specs=nosys.specs --specs=nano.specs -mcpu=cortex-m4 .pio/build/commonsense/src/main.o -L.pio/build/commonsense -L/Users/work-reese/.platformio/packages/toolchain-gccarmnoneeabi/arm-none-eabi/lib -L/Users/work-reese/.platformio/packages/framework-cmsis/CMSIS/Lib/GCC -L/Users/work-reese/.platformio/packages/framework-commonsense/linker -Wl,--start-group .pio/build/commonsense/libFrameworkCommonSense.a -larm_cortexM4lf_math -lc_nano -lm -Wl,--end-group

这是使用 arm 交叉编译器 7.2.1 版,工具链包含 libc、libc_nano、libm 等的分发版。所有必需的库似乎都存在。

请注意,我在上面为自定义版本的链接添加了一些额外的行,因此您可以看到它正在构建什么libFrameworkCommonSense.a。这些文件中的 None 包含任何 stdlib 调用,尽管 cortex_handlers 在重置处理程序中没有 __libc_init_array 因为这也会以与 memset 相同的方式导致硬故障。两者之间的链接描述文件是相同的;我再次从 adafruit 项目大量借用中断处理程序和启动代码,但直到现在我还没有看到环境之间的任何实际差异。

添加 --print-multi-lib 选项显示了几个应该起作用的选项,即 thumb/v7e-m/fpv4-sp/softfp;@mthumb@march=armv7e-m@mfpu=fpv4-sp-d16@mfloat-abi=softfp 应该根据编译器标志选择。奇怪的是,它在打印 multilib 选项时编译失败,理由是要存档的目标文件 (arm-none-eabi-ar) 不存在于构建目录中。这可能无关紧要。

这是主文件的编译,其中包括对 memset 和 memcpy 的调用:

arm-none-eabi-gcc -o .pio/build/commonsense/src/main.o -c -std=gnu11 -mfpu=fpv4-sp-d16 -Og -g3 -mlong-calls --specs=nano.specs -specs=nosys.specs -fdata-sections -ffunction-sections -mfloat-abi=softfp -march=armv7e-m -mfpu=fpv4-sp-d16 -marm -mthumb-interwork -ffunction-sections -fdata-sections -Wall -mthumb -nostdlib --param max-inline-insns-single=500 -mcpu=cortex-m4 -DPLATFORMIO=50003 -D__SAMD51P20A__ -D__SAMD51__ -D__FPU_PRESENT -DARM_MATH_CM4 -DENABLE_CACHE -DVARIANT_QSPI_BAUD_DEFAULT=50000000 -DDEBUG -DADAFRUIT_LINKER -DF_CPU=120000000L -Iinclude -Isrc -I/Users/work-reese/.platformio/packages/framework-cmsis/CMSIS/Include -I/Users/work-reese/.platformio/packages/framework-cmsis-atmel/CMSIS/Device/ATMEL -I/Users/work-reese/.platformio/packages/framework-cmsis-atmel/CMSIS/Device/ATMEL/samd51 -I/Users/work-reese/.platformio/packages/framework-commonsense -I/Users/work-reese/.platformio/packages/framework-commonsense/core -I/Users/work-reese/.platformio/packages/framework-commonsense/hal -I/Users/work-reese/.platformio/packages/framework-commonsense/hal/include -I/Users/work-reese/.platformio/packages/framework-commonsense/hal/utils/include -I/Users/work-reese/.platformio/packages/framework-commonsense/hal/utils/src -I/Users/work-reese/.platformio/packages/framework-commonsense/hal/src -I/Users/work-reese/.platformio/packages/framework-commonsense/hpl -I/Users/work-reese/.platformio/packages/framework-commonsense/hri -I/Users/work-reese/.platformio/packages/framework-commonsense/sample src/main.c

有谁知道为什么我会因为错误链接的库函数而出现这种行为?我已经对它进行了将近一个星期的抨击,向它抛出许多编译器标志的组合都无济于事。我觉得有些东西我忽略了,但不知道是什么。我很高兴提供任何其他信息。

旁问:什么是__libc_init_array(),在程序启动时有必要调用吗?我在 adafruit 和 Atmel Studio 项目的重置处理程序中看到了这一点。它在其启动文件中在本地声明为函数原型,但在我自己的环境中重现相同的东西会在处理器尝试调用该函数时立即导致硬故障。我应该认为它是 libc 或类似的一部分。

似乎问题出在尝试使用编译器标志 -mfloat-abi=softfp 时。我切换到 -mfloat-abi=hard,这些链接问题似乎消失了。我确认错误的开关集也会破坏 adafruit 环境。

根据我是只使用硬件进行浮点运算还是混合使用 SW 仿真和 HW 进行 FP,我会出现这样的错误似乎仍然很奇怪。 None 我的代码也使用了浮点数。

我设置为 'softfp' 的部分原因是我发现提到的 FreeRTOS 端口我应该使用这个开关。希望这不会妨碍我使用它。

我的问题仍然存在于 __libc_init_array() 上,因为当我 运行 它时它仍然会产生硬故障 - 它的反汇编看起来也很奇怪,分支到奇怪的地方(即,异常 table).

I have found that calls to standard C functions like memset, memcpy are generating bogus code. The disassembly shows that instructions in both those functions (and others I've tried from stdlib) branch unconditionally to locations that contain no code, which of course causes the MCU (a Cortex M4, the Atmel D51) to hard-fault as it tries executing nonsense code.

实际上,那是ARM代码而不是thumb代码。当你试图将它作为 thumb 进行反汇编时,这是胡说八道,但是将它作为 ARM 进行反汇编时看起来似乎有道理。

当然,您的处理器不能执行 ARM 代码,只能执行拇指代码,无论如何,即使是在 ARM 模式 中也可能遇到它的处理器。所以硬故障没有什么神秘的。

不清楚的是您是如何在 thumb 项目中使用 ARM 代码的。乍一看,您对编译器的实际调用似乎指定了 thumb,所以我猜问题代码实际上是由于链接了错误的库而到达的。