Arrays 交叉编译裸金属ARM时未按预期初始化C数组

Arrays 交叉编译裸金属ARM时未按预期初始化C数组,arrays,c,gcc,arm,cross-compiling,Arrays,C,Gcc,Arm,Cross Compiling,我的目标是为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

我的目标是为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中使用
print
display
时,我总是得到
a={0,0,0,0}
。我可以将分配放入循环中,或者对数组执行其他操作。GDB在赋值前后都显示
a={0,0,0,0,0}

当我为x86编译代码并以本机方式运行时,没有问题

这相当复杂,因此我考虑了一些可能的原因:

  • 代码是错误的
  • 工具链的编译过程/使用是错误的
  • QEMU的使用方式是错误的
  • 程序运行良好,但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系统arm
    GCC arm无eabi

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

    编辑0:

    回应paxdiablo关于入口点的想法: 我从劳工处得到这个警告:
    arm none eabi ld:警告:找不到输入符号\u start;默认值为0000000000008000
    这个地址实际上是存储
    main
    的地方

    关于Eric Postdischil提到的创建一个未使用的、因此优化的out数组的主题: 我已经用
    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&a
    x0xffffffe8
    (这是我得到的地址),但结果显然是一样的-数组a在“赋值”前后都保留零。

    答案是:

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

    您可能有几个问题,一个重要的问题是使用
    -kernel
    选项要求程序的起始地址为
    0x00010000
    。 您没有启动文件,也没有链接器脚本

    下面的示例应该可以很好地工作,它是根据Francesco Balducci的一篇文章改编的

    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 = .;
    }
    
    测试c:

    volatile unsigned int * const UART0DR = (unsigned int *)0x101f1000;
     
    void print_uart0(const char *s) {
     while(*s != '\0') { /* 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!
    

    如图所示的程序不“执行”任何操作。按照C标准的规定,C程序“做”的唯一事情是可观察的行为。这包括写入文件、输入/输出交互以及访问易失性对象。您的程序没有这些功能,因此编译器不需要生成代码来使用
    a
    执行任何操作。将
    inta[5]
    更改为
    volatile inta[5]
    ,看看会发生什么。您看到的汇编代码似乎正在某处存储所需的值这一事实很有趣,因为它表明编译器正在生成代码来初始化
    a
    。但也许这不是代码所做的。也许这只是一些准备代码,而将值存储在正式保留给
    a
    的内存中的代码(如报告给gdb的)被优化掉了。因此,gdb从未看到这些值。更改编译器优化设置可能会导致不同的行为。@Eric,这似乎不太可能,因为objdump显示堆栈上的值
    2
    3
    相隔一个整数,就像填充数组一样。我同意可以对整个过程进行优化,但从问题来看,情况似乎并非如此。问题源于一段代码,该代码实际上在某种程度上利用了数组(例如,它作为参数传递给其他函数),结果是相同的。我还使用了
    volatile
    并禁用了GCC中的优化,问题仍然存在,我可能会添加一个第5点。qemu系统arm运行的内核的入口点是什么?通常,编译C代码会带来大量的东西来建立C运行时环境,比如
    crt0
    和标准库。这意味着入口点通常类似于
    \u start
    ,而不是
    \u main
    ,并且一旦一切就绪,前者调用后者。比如说,,