【问题标题】:avr-gcc destructive optimizationsavr-gcc 破坏性优化
【发布时间】:2013-12-03 16:46:00
【问题描述】:

我正在使用 avr-gcc 4.8.2 对 Atmel ATtiny13a 微控制器进行编程。

这是我的 c 代码:

#include <avr/io.h> 
#include <util/delay.h> 

int main(void) {
    DDRB = 1; // PB0 is output
    for (uint8_t i = 0; i < 10; i++) {
        PORTB = 1;
        _delay_ms(500);
        PORTB = 0;
        _delay_ms(500);
    }
    while(1);
}

void test(void) {
    DDRB = 1; // PB0 is output
    for (uint8_t i = 0; i < 10; i++) {
        PORTB = 1;
        _delay_ms(100);
        PORTB = 0;
        _delay_ms(100);
    }
}

从不从主函数调用测试函数(LED 快速闪烁),因此控制器应该只进入主函数(慢速闪烁)。

当我使用-O1 编译代码时,一切正常:

avr-gcc -std=gnu99 -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -mmcu=attiny13 -DF_CPU=1200000   -Wall -Wstrict-prototypes -Os -c test.c -o test.o
avr-gcc  test.o -o test.elf
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature test.elf test.hex

但如果我使用-Os(尺寸优化)或-O2,微控制器会运行test 函数而不是main 函数:LED 快速闪烁并且永不停止。

-Os 标志是否太危险而无法使用,是否应该避免使用?或者我可以在我的代码中改变什么来避免这种错误? ATtiny13a 只有 1K 的闪存,因此减小尺寸很重要。


编辑:正如 cmets 中所建议的,这是 -O1-O2 的汇编程序差异:http://www.diffchecker.com/3l9cdln6

在那里您可以看到-O2 将第一部分从.text 更改为.text.startup

--- test.o1.txt 2013-12-03 19:10:43.874598682 +0100
+++ test.o2.txt 2013-12-03 19:10:50.574674155 +0100
@@ -3,7 +3,7 @@
 __SREG__ = 0x3f
 __tmp_reg__ = 0
 __zero_reg__ = 1
-       .text
+       .section        .text.startup,"ax",@progbits
 .global        main
        .type   main, @function
 main:

这可能是这里的主要问题。经过进一步测试,我发现罪魁祸首是-freorder-functions 优化。有没有办法防止这种行为?

【问题讨论】:

  • 也许考虑使用-S 选项进行编译,仅编译为程序集。您可以查看或发布-Os-O2 的程序集,也许会有一些明显的东西。
  • @Macattack:谢谢,你很可能为我指明了正确的方向。请参阅原始帖子中的编辑以了解差异。
  • 这里有一些(nongnu 文档)[nongnu.org/avr-libc/user-manual/mem_sections.html] 提到.text,但信息充其量是稀疏的。知道编译后的步骤中发生了什么吗?我对这些构建步骤知之甚少。
  • @DaniloBargen 您在差异中显示的代码在-O1-O2 之间是相同的(仅更改标签名称)。相同的代码怎么会有不同的行为?

标签: c compiler-optimization avr-gcc


【解决方案1】:

我做了一些进一步的调试,发现“罪魁祸首”是-freorder-functions优化。它在手册页中记录如下:

-freorder-functions
    Reorder functions in the object file in order to improve code locality.
    This is implemented by using special subsections ".text.hot" for most
    frequently executed functions and ".text.unlikely" for unlikely executed
    functions. Reordering is done by the linker so object file format must
    support named sections and linker must place them in a reasonable way.

文档的最后一行解释了我遇到/导致的问题。如果我们再次查看原始问题中的编译命令:

$ avr-gcc -std=gnu99 -funsigned-char -funsigned-bitfields -fpack-struct \
   -fshort-enums -mmcu=attiny13 -DF_CPU=1200000   -Wall -Wstrict-prototypes \
   -Os -c test.c -o test.o
$ avr-gcc  test.o -o test.elf

...我们看到我将优化标志传递给编译器,但没有传递给链接器。我假设CFLAGS只影响编译而不影响链接,所以我没有将它们传递给链接器,但在那种情况下我错了。

结果:编译器对汇编代码重新排序(包括适当的标签),但链接器没有考虑这些标签。而且因为test 函数被编译器放置在main 函数之前,并且没有被链接器重新排列,这就是在微控制器上实际执行的代码。

所以解决方案是:编译器标志也应该传递给链接器!

【讨论】:

  • 你刚刚为我节省了查找错误的时间。对此表示敬意。
【解决方案2】:

我知道我的回复是在提出这个问题大约 2 年后才做出的,但我相信仍然没有正确、深入的答案。


让我们从一点理论开始:

当您调用 GCC 时,它通常会进行预处理、编译、汇编和链接。 “整体选项”允许您在中间阶段停止此过程。例如,-c 选项表示不运行链接器。然后输出由汇编器输出的目标文件组成。

其他选项被传递到一个处理阶段。一些选项控制预处理器,而另一些选项控制编译器本身。还有其他选项控制汇编器和链接器;其中大部分没有在此处记录,因为您很少需要使用它们中的任何一个。

来源:GCC Online Docs

LDFLAGS

在编译器应该调用链接器时提供给编译器的额外标志,“ld”,例如 -L。应该将库 (-lfoo) 添加到 LDLIBS 变量中。

来源:GNU make Manual

如您所见,这取决于 GCC(我将这样称呼它以区别于实际的编译器;您可以发现它被称为 C 编译器前端 或简称为 compiler em> 虽然)哪些选项将传递给哪些工具,并且似乎 -On 选项没有传递给链接器(您可以通过将 -v 选项提供给 GCC 来检查它)。所以当它应该只做链接时调用没有这个选项的GCC是可以的。

真正的问题是您在链接时没有向 GCC 提供-mmcu=dev 选项。因此无法找到正确的crt*.o 文件(C 运行时)并告诉链接器将其链接;您的应用程序最终没有任何初始化代码。

因此请注意,您必须在 LDFLAGS 中包含 -mmcu=dev 或将其传递给 GCC,而不管它的用途是什么(预处理/编译/组装/链接)。我已经在 Internet 上的 LDFLAGS 中看到了几个没有此选项的 makefile,所以要小心。


现在是练习的时候了——假设您的源代码在test.c 文件中,发出以下命令(在 linux 上):

avr-gcc -mmcu=attiny13a -DF_CPU=1200000 -Wall -O1 -c -o testO1.o test.c
avr-gcc -mmcu=attiny13a -DF_CPU=1200000 -Wall -Os -c -o testOs.o test.c
avr-gcc -o testO1_nodev.elf testO1.o
avr-gcc -v -o testOs_nodev.elf testOs.o > testOs_nodev.log 2>&1
avr-gcc -v -mmcu=attiny13a -o testOs_correct.elf testOs.o > testOs_correct.log 2>&1

我只留下了必要的选项 + -Wall,对于 ATtiny13a,你需要 -mmcu=attiny13a 而不是 -mmcu=attiny13

让我们看看testOs_nodev.logtestOs_correct.log。发出以下命令:

diff testOs_nodev.log testOs_correct.log

你会看到类似的东西:

2c2
< Reading specs from /usr/lib/gcc/avr/5.2.0/device-specs/specs-avr2
---
> Reading specs from /usr/lib/gcc/avr/5.2.0/device-specs/specs-attiny13a
10,12c10,12
< LIBRARY_PATH=/usr/lib/gcc/avr/5.2.0/:/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/
< COLLECT_GCC_OPTIONS='-v' '-o' 'testOs_nodev.elf' '-specs=device-specs/specs-avr2'
<  /usr/lib/gcc/avr/5.2.0/collect2 -plugin /usr/lib/gcc/avr/5.2.0/liblto_plugin.so \
-plugin-opt=/usr/lib/gcc/avr/5.2.0/lto-wrapper -plugin-opt=-fresolution=/tmp/ccqBjM6T.res \
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lm -plugin-opt=-pass-through=-lc \ 
-o testOs_nodev.elf -L/usr/lib/gcc/avr/5.2.0 -L/usr/lib/gcc/avr/5.2.0/../../../../avr/lib \
testOs.o --start-group -lgcc -lm -lc --end-group
---
> LIBRARY_PATH=/usr/lib/gcc/avr/5.2.0/avr25/tiny-stack/:\
/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/avr25/tiny-stack/:\
/usr/lib/gcc/avr/5.2.0/:/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/
> COLLECT_GCC_OPTIONS='-v'  '-o' 'testOs_correct.elf' '-specs=device-specs/specs-attiny13a' \
'-mmcu=avr25' '-msp8'
>  /usr/lib/gcc/avr/5.2.0/collect2 -plugin /usr/lib/gcc/avr/5.2.0/liblto_plugin.so \
-plugin-opt=/usr/lib/gcc/avr/5.2.0/lto-wrapper -plugin-opt=-fresolution=/tmp/ccV919rY.res \
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lm -plugin-opt=-pass-through=-lc \
-plugin-opt=-pass-through=-lattiny13a -mavr25 -o testOs_correct.elf \
/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/avr25/tiny-stack/crtattiny13a.o \
-L/usr/lib/gcc/avr/5.2.0/avr25/tiny-stack -L/usr/lib/gcc/avr/5.2.0/../../../../avr/lib/avr25/tiny-stack \
-L/usr/lib/gcc/avr/5.2.0 -L/usr/lib/gcc/avr/5.2.0/../../../../avr/lib testOs.o \
--start-group -lgcc -lm -lc -lattiny13a --end-group

(为了便于阅读,我断了几行)

不同的是,没有-mmcu=dev选项的GCC默认使用avr2 specs文件并且不链接任何CRT文件。

使用 avr-objdump 检查目标文件 (*.o) 和输出文件 (*.elf):

avr-objdump -xd testOs_nodev.elf

您会注意到*_nodev.elf 文件不包含有关架构的正确信息(avr 而不是 avr:25),也不包含任何启动代码(比较 testOs_correct.elftestOs_nodev.elf)。代码部分似乎是目标文件中提供的内容的逐字副本。


如果我的阐述中的任何部分似乎不清楚或需要额外解释,请随时提问(评论)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-09
    • 1970-01-01
    • 2015-12-25
    • 2016-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多