【问题标题】:C array not initialized as expected when cross-compiling for baremetal ARM为裸机 ARM 进行交叉编译时,C 数组未按预期初始化
【发布时间】:2021-08-11 03:54:46
【问题描述】:

我的目标是为 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: cmets中提到,可以在GDB中查看数组的地址,然后转储内存,而不是要求print a。 我用过print &ax 0xffffffe8(这是我得到的地址),但显然结果是一样的——数组a 在“赋值”之前和之后都保持零。

【问题讨论】:

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

标签: arrays c gcc arm cross-compiling


【解决方案1】:

答案是:

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

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

以下示例应该可以正常工作,并且只是改编自 Francesco Balducci 在他的blog 上的seminal article

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 != '\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!

【讨论】:

    猜你喜欢
    • 2015-07-19
    • 1970-01-01
    • 2013-07-04
    • 2013-02-20
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 2011-07-24
    相关资源
    最近更新 更多