avr-gcc破坏性优化

时间:2013-12-03 16:46:00

标签: c compiler-optimization avr-gcc

我正在使用avr-gcc 4.8.2编写Atmel ATtiny13a微控制器。

这是我的代码:

#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的闪光灯,所以尺寸减小很重要。


编辑:根据评论中的建议,这里是汇总差异-O1-O2http://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优化。有没有办法防止这种行为?

2 个答案:

答案 0 :(得分:7)

我做了一些进一步的调试,发现“罪魁祸首”是-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函数之前而没有被链接器重新排列,这就是在微控制器上实际执行的代码。

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

答案 1 :(得分:3)

我知道自从提出这个问题以来,我的回答是在2年之后,但我相信仍然没有正确,深入的答案。

让我们从一点理论开始:

  

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

     

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

来源:GCC Online Docs

  

LDFLAGS

     

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

来源:GNU make Manual

正如你所看到的,它取决于GCC(我会这样称呼它以区别于实际的编译器;你可以发现它叫做 C编译器前端或只是编译器虽然)哪些选项将传递给哪些工具,并且看起来-On选项未传递给链接器(您可以通过向GCC提供-v选项来检查它)。所以如果只做链接就调用没有这个选项的GCC就行了。

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

因此请注意,您必须在LDFLAGS中包含-mmcu=dev或将其传递给GCC,无论其意图是什么(预处理/编译/组合/链接)。我已经在互联网上的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进行比较)。代码部分似乎是对象文件中提供的内容的逐字副本。

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