为裸机 ARM 交叉编译时 C 数组未按预期初始化

C array not initialized as expected when cross-compiling for baremetal ARM

我的目的是为ARM实现一个简单的裸机程序,手动编译并在GDB中分析。 显示我的问题的一个简单示例 main.c 是:

int main(){
    int a[5] = {0};

    a[0] = 1;
    a[1] = 2;
    a[2] = 3;
    a[3] = 4;
    a[4] = 5;

    return 0;
}

我是这样编译的:

arm-none-eabi-gcc -c -g main.c
arm-none-eabi-ld main.o -o main.elf

运行 它在 QEMU 中:

qemu-system-arm -M versatilepb -nographic -kernel main.elf -S -s

当我在 GDB 中使用 printdisplay 时,我总是得到 a = {0, 0, 0, 0, 0}。我可以将赋值放在循环中或对数组做其他事情。 GDB 在赋值前后显示 a = {0, 0, 0, 0, 0}

当我为 x86 和 运行 本地编译代码时,没有问题。

这很复杂,所以我考虑了一些可能的原因:

  1. 代码有误,
  2. 工具链的编译过程/使用错误,
  3. QEMU使用错误
  4. 程序运行良好,但 GDB 显示错误值

我排除了 (1.) 因为它适用于 x86。为了排除 (2.) 我查看了 ELF 文件的 objdump,其中的一部分可以在这里看到:

    a[1] = 2;
    8030:   e3a03002    mov r3, #2
    8034:   e50b3014    str r3, [fp, #-20]  ; 0xffffffec
    a[2] = 3;
    8038:   e3a03003    mov r3, #3
    803c:   e50b3010    str r3, [fp, #-16]

看起来这些值确实分配给了数组的元素。

排除 (4.) 我编译了最新的 GDB,支持所有可用目标。 (GNU gdb (GDB) 11.0.50.20210521-git)

QEMU 和 GCC 取自 Ubuntu 包 qemu-system-armgcc-arm-none-eabi.

为什么结果与我的预期不同? (1. - 4.) 中的任何一个作为问题的潜在根源是否有意义?我试图真正理解这背后的逻辑,而不仅仅是拥有一个数组。

编辑0:

回应paxdiablo关于入口点的想法: 我从 ld 收到此警告: arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000 而这个地址其实就是存放main的地方

关于 Eric Postpischil 提到的创建未使用的因此优化的数组的主题: 我用 volatile 关键字检查了结果,还使用了 GCC 的 -O0 选项。不幸的是,这些并没有改变任何东西。

通过对 gcc 使用 -S 选项检索的整个程序集是:

    .cpu arm7tdmi
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 6
    .eabi_attribute 34, 0
    .eabi_attribute 18, 4
    .file   "test.c"
    .text
    .section    .rodata
    .align  2
.LC0:
    .word   1
    .word   2
    .word   3
    .word   4
    .text
    .align  2
    .global main
    .arch armv4t
    .syntax unified
    .arm
    .fpu softvfp
    .type   main, %function
main:
    @ Function supports interworking.
    @ args = 0, pretend = 0, frame = 16
    @ frame_needed = 1, uses_anonymous_args = 0
    @ link register save eliminated.
    str fp, [sp, #-4]!
    add fp, sp, #0
    sub sp, sp, #20
    ldr r3, .L3
    sub ip, fp, #20
    ldm r3, {r0, r1, r2, r3}
    stm ip, {r0, r1, r2, r3}
    mov r3, #5
    str r3, [fp, #-20]
    mov r3, #4
    str r3, [fp, #-16]
    mov r3, #3
    str r3, [fp, #-12]
    mov r3, #2
    str r3, [fp, #-8]
    mov r3, #0
    mov r0, r3
    add sp, fp, #0
    @ sp needed
    ldr fp, [sp], #4
    bx  lr
.L4:
    .align  2
.L3:
    .word   .LC0
    .size   main, .-main
    .ident  "GCC: (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]"

编辑1: 正如评论中提到的,可以在GDB中查看数组的地址,然后转储内存,而不是要求print a。 我使用了 print &ax 0xffffffe8(这是我得到的地址),但显然结果是一样的——数组 a 在 'assignments'.[=33 之前和之后都为零=]

答案是:

2)工具链的编译过程/使用方法错误

您可能会遇到几个问题,一个重要的问题是使用 -kernel 选项需要程序的起始地址为 0x00010000。 而且您没有启动文件,也没有链接描述文件。

以下示例应该可以正常工作,并且只是改编自 seminal article from Francesco Balducci on his blog

startup.s:

.global _Reset
_Reset:
 LDR sp, =stack_top
 BL c_entry
 B .

test.ld:

ENTRY(_Reset)
SECTIONS
{
 . = 0x10000;
 .startup . : { startup.o(.text) }
 .text : { *(.text) }
 .data : { *(.data) }
 .bss : { *(.bss COMMON) }
 . = ALIGN(8);
 . = . + 0x1000; /* 4kB of stack memory */
 stack_top = .;
}

test.c:

volatile unsigned int * const UART0DR = (unsigned int *)0x101f1000;
 
void print_uart0(const char *s) {
 while(*s != '[=12=]') { /* Loop until end of string */
 *UART0DR = (unsigned int)(*s); /* Transmit char */
 s++; /* Next char */
 }
}
 
void c_entry() {
    int a[5] = {0};

    a[0] = 1;
    a[1] = 2;
    a[2] = 3;
    a[3] = 4;
    a[4] = 5;

    if (a[0] == 1 && a[1] == 2 && a[2] == 3 && a[3] == 4 && a[4] == 5) {
        print_uart0("Hello world!\n");
    }

    return 0;
}

build.sh:

#!/bin/bash
CROSS_COMPILE=/opt/arm/10/gcc-arm-10.2-2020.11-x86_64-arm-none-eabi/bin/arm-none-eabi-
QEMU_SYSTEM_ARM=/opt/qemu-6.0.0/bin/qemu-system-arm

${CROSS_COMPILE}as -mcpu=arm926ej-s -g startup.s -o startup.o
${CROSS_COMPILE}gcc -c -mcpu=arm926ej-s -g test.c -o test.o
${CROSS_COMPILE}ld -T test.ld test.o startup.o -o test.elf

${QEMU_SYSTEM_ARM} -M versatilepb -m 128M -nographic -kernel test.elf

执行:(修复编译警告和禁用声音模拟超出了当前答案的范围)

./build.sh

test.c: In function 'c_entry':
test.c:23:12: warning: 'return' with a value, in function returning void
   23 |     return 0;
      |            ^
test.c:10:6: note: declared here
   10 | void c_entry() {
      |      ^~~~~~~
pulseaudio: set_sink_input_volume() failed
pulseaudio: Reason: Invalid argument
pulseaudio: set_sink_input_mute() failed
pulseaudio: Reason: Invalid argument
Hello world!