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