使用-Os编译时无法写入LD链接脚本定义的变量问题排查
裸机程序LD链接脚本变量写入的优化问题
编写裸机程序时,尝试向自定义LD链接脚本中定义的符号地址写入数据,使用-O0编译时运行正常,但-Os编译下写入语句被优化,结果不符合预期。
问题代码与配置
main.c(原写法)
#define TTB_BASE (&Image$$TTB) extern unsigned int Image$$TTB; int main () { *TTB_BASE = 56326; unsigned int *ttb=TTB_BASE+16; for (int i = 0; i < 8; i++ ) { *ttb++ = 1; } *(volatile unsigned int*)(0x00100000) = *TTB_BASE; }
链接脚本ARMCA7.ld
ENTRY(main) SECTIONS { . = 0x00160000; .text : { *(.text*) } .data : { *(.data .data.* .gnu.linkonce.d*) } .bss (NOLOAD) : { . = ALIGN(16); *(.bss .bss.*) *(COMMON) } . = 0x00170000; .ttb : { Image$$TTB = .; } }
编译命令
arm-none-eabi-gcc -mcpu=cortex-a7 -Os -std=gnu99 -c -o main.o main.c arm-none-eabi-gcc -mcpu=cortex-a7 -Os -T ARMCA7.ld -nostartfiles -Xlinker --gc-sections -Wl,-Map,"main.map" -o main.elf main.o
反汇编结果
Address : Opcode Statement ------- ------ --------- 8 unsigned int *ttb=TTB_BASE+16; main: 00160000: 24 30 9f e5 ldr r3, [pc, #36] ; 0x16002c <main+44> 12 *ttb++ = 1; 00160004: 01 10 a0 e3 mov r1, #1 00160008: 20 20 83 e2 add r2, r3, #32 0016000c: 04 10 83 e4 str r1, [r3], #4 10 for (int i = 0; i < 8; i++ ) 00160010: 02 00 53 e1 cmp r3, r2 00160014: fc ff ff 1a bne 0x16000c <main+12> 14 *(volatile unsigned int*)(0x00100000) = *TTB_BASE; 00160018: 60 20 13 e5 ldr r2, [r3, #-96] ; 0xffffffa0 0016001c: 01 36 a0 e3 mov r3, #1048576 ; 0x100000 15 } 00160020: 00 00 a0 e3 mov r0, #0 14 *(volatile unsigned int*)(0x00100000) = *TTB_BASE; 00160024: 00 20 83 e5 str r2, [r3] 15 } 00160028: 1e ff 2f e1 bx lr 0016002c: 40 00 17 00 andseq r0, r7, r0, asr #32
从反汇编可见,没有任何操作写入链接脚本定义的0x00170000地址,最终写入0x00100000的值也不正确。
问题原因分析
链接脚本中Image$$TTB = .;定义的是一个符号地址标记,而非实际的变量存储空间。原代码中extern unsigned int Image$$TTB;告诉编译器这是一个unsigned int类型的变量,但实际上该符号只是一个地址值,没有对应的内存区域。
-O0编译时,编译器不做优化,会严格执行所有内存读写操作;-Os(空间优化)时,编译器会分析代码逻辑:认为对*TTB_BASE的写入是无意义操作(因为没有合法的后续读取依赖),直接优化掉写入指令,导致目标地址未被赋值。
而修改后的写法:
#define TTB_BASE (Image$$TTB) extern unsigned int Image$$TTB[];
将Image$$TTB声明为数组,编译器会将其视为指向内存区域的指针符号,而非具体变量值,因此不会优化掉对该地址的读写操作,能正确访问目标地址。
解决方案
以下三种方式均可解决优化问题:
1. 数组声明符号
extern unsigned int Image$$TTB[]; #define TTB_BASE (Image$$TTB)
明确告知编译器Image$$TTB是内存区域的起始地址,编译器会正确生成读写指令。
2. 用volatile强制保留操作
extern volatile unsigned int Image$$TTB; #define TTB_BASE (&Image$$TTB)
volatile关键字会强制编译器保留对该地址的读写操作,避免被优化。
3. 链接脚本分配实际存储空间
在.ttb段中分配真实内存,让符号指向有效区域:
.ttb : { Image$$TTB = .; . += 0x400; /* 按需分配空间,示例为1KB */ }
此时编译器会认为该地址对应有效内存,不会优化读写操作。
测试环境
使用的GCC版本:
arm-none-eabi-gcc --version arm-none-eabi-gcc.exe (Arm GNU Toolchain 11.3.Rel1) 11.3.1 20220712 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
同时在10.3.1版本中也复现了相同问题。
内容的提问来源于stack exchange,提问作者Feihu Liu
相关产品推荐
相关产品推荐

