gcc 使用-Os选项编译时,无法写入LD链接器脚本中定义的变量

sshcrbum  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(186)

我正在写一个运行裸机的程序。我试图从一个自定义链接器脚本中写入一个变量。当用-O 0选项编译时,代码运行得很好,但当用-Os选项编译时,代码运行得不像预期的那样。
我使用的代码如下。
主.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;
}

当用-Os选项编译时,代码**TTB_BASE = 56326;* 似乎被优化掉了,并且该值没有被存储到地址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

可以看出,在链接器脚本中定义的地址TTB_BASE为0x 00170000,没有写入任何内容,写入地址0x 00100000的值是错误的。
代码或链接器脚本中是否存在错误?

备注:

如果将 main.c 的前两行代码更改为以下代码,

#define TTB_BASE (Image$$TTB)
extern unsigned int Image$$TTB[];

代码可以被正确编译,地址TTB_BASE也写得正确。但是,正如我从other questions看到的,这两种方法都应该是正确的。
使用的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.

我还使用arm-none-eabi-gcc.exe 10.3.1 20210621(发布版本)测试了代码,并得到了相同的问题。

bmp9r5qi

bmp9r5qi1#

您的外部变量对编译器没有可见的 * 副作用 *,因此允许优化赋值。
避免这种情况的标准方法是添加限定符volatile。它告诉编译器该变量有副作用。因此,它不能优化赋值。

extern volatile unsigned int Image$$TTB;

如果您有多个线程(如中断服务例程),这些线程彼此共享变量或与主线程共享变量,则也需要此限定符。
增加:
正如其他人在评论中指出的那样,链接器脚本中有一个错误,你没有为变量保留空间。

zfycwa2u

zfycwa2u2#

我发现发布的问题与变量引用有关,而不是与链接器脚本有关。请参考此new post以获得进一步的讨论。
作为一个结论,代码不起作用的真实的原因如下。&Image$$TTB+16是 * Undefined Behavior in C *。
也就是说,如果要更改链接器脚本文件中定义的地址区域,则需要以下声明。

extern unsigned int Image$$TTB[];

下面是另一个有同样bug的示例代码。注意,它应该用-O 1或更高版本编译。你可以尝试一下at Compiler Explorer

#include <stdio.h>
unsigned int var;
volatile unsigned int valid_address[1024];
int main ()
{
    var = 56326;
    unsigned int *ttb=&var;
    ttb += 16;
    for (int i = 0; i < 8; i++ )
    {
        *ttb++ = 1;
    }
    valid_address[0] = var;
    printf("Value is: %d", valid_address[0]);
}

如果执行此代码,可能会得到以下输出:

Value is: 0

相关问题