为裸机 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 中使用 print
或 display
时,我总是得到 a = {0, 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-system-arm
、gcc-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 &a
和 x 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!
我的目的是为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, 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-system-arm
、gcc-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 &a
和 x 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!