AVR-GCC破坏性优化

4

我正在使用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的flash,因此大小缩小很重要。

编辑:根据评论建议,以下是使用-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优化。有没有办法防止这种行为?


1
也许考虑使用“-S”选项进行编译,仅将其编译为汇编。您可以查看或发布“-Os”和“-O2”的汇编内容,也许会有一些明显的东西。 - Macattack
@Macattack:谢谢,你可能指引了我正确的方向。请查看原帖编辑以获取差异。 - Danilo Bargen
这里有一些(nongnu文档)[http://www.nongnu.org/avr-libc/user-manual/mem_sections.html]提到了`.text`,但信息最多也很稀少。在编译后的步骤中发生了什么,有任何想法吗?我对那些构建步骤一无所知。 - Macattack
@DaniloBargen,你在差异中展示的代码在 -O1-O2 之间是相同的(只有标签名称改变)。你怎么可能有不同的行为却使用相同的代码? - ouah
2个回答

11

我进行了进一步的调试,发现"罪魁祸首"是-freorder-functions优化。它在man手册中有如下文档:

-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
你刚刚帮我省去了找错误的艰难时光,非常感谢你。 - sitilge

4

我知道我回答这个问题已经过去了大约2年,但我认为仍然没有正确深入的答案。


让我们从一点理论开始:
当您调用GCC时,它通常会进行预处理、编译、汇编和链接。 "总体选项" 允许您在中间阶段停止此过程。例如,-c选项表示不运行链接器。然后输出由汇编器输出的目标文件。
其他选项传递给处理的某个阶段。有些选项控制预处理器,其他选项控制编译器本身。还有其他选项控制汇编器和链接器;这些大多数都没有在此处记录,因为您很少需要使用它们。
来源:GCC Online Docs LDFLAGS
给编译器在它们应该调用链接器 'ld' 时提供额外的标志,例如 -L。 库(-lfoo)应添加到LDLIBS变量中。
来源:GNU make Manual 正如你所看到的,这取决于GCC(我将称之为这种方式以区别于实际编译器;你可以发现它被称为C编译器前端或简单地称为编译器)哪些选项将传递给哪些工具,似乎-On选项没有传递给链接器(你可以通过给GCC加上选项-v来检查它)。因此,在只进行链接时调用GCC时不需要使用该选项。

真正的问题是在链接时没有向GCC提供-mmcu=dev选项。因此,它无法找到适当的文件(C运行时),也无法告诉链接器将其链接起来;你的应用程序最终没有任何初始化代码。

因此,请注意,无论它的用途是什么(预处理/编译/汇编/链接),都必须在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规范文件并不链接任何CRT文件。

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

avr-objdump -xd testOs_nodev.elf

您会发现*_nodev.elf文件不包含有关架构的正确信息(avr而不是avr:25),也没有任何启动代码(将testOs_correct.elftestOs_nodev.elf进行比较)。代码部分似乎是从对象文件中提供的逐字复制。
如果我的解释有任何不清楚或需要额外的说明,请随时提问(评论)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接