GCC能否打印出中间结果?

7

请查看下面的代码:

#include <avr/io.h>

const uint16_t baudrate = 9600;

void setupUART( void ) {
        uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
        UBRRH = ubrr >> 8;
        UBRRL = ubrr & 0xff;
}

int main( void ) {
        setupUART();
}

这是用于编译代码的命令:

avr-gcc -g -DF_CPU=4000000       -Wall -Os -Werror -Wextra -mmcu=attiny2313 -Wa,-ahlmns=project.lst -c -o project.o project.cpp

ubrr被编译器计算为25,目前看起来很好。不过,为了检查编译器计算的内容,我需要查看反汇编列表。

000000ae <setupUART()>:
  ae:   12 b8           out     UBRRH, r1       ; 0x02
  b0:   89 e1           ldi     r24, 0x19       ; 25
  b2:   89 b9           out     UBRRL, r24      ; 0x09
  b4:   08 95           ret

有没有可能让avr-gcc编译时打印出中间结果(或从.o文件中提取信息),这样当我编译代码时,它会打印出类似于(uint16_t)ubbr = 25的行? 这样我就可以快速检查计算和设置是否正确。


你试过使用-S选项吗? - devnull
@devnull 这不会退出编译器吗?我希望编译器能完成工作,只是打印出它所做的中间计算。 - jippie
如果你不使用-Os参数,你可能会在反汇编中看到你想要的内容。但是,你可能不希望以这种方式发布代码。;-) - Carl Norum
@CarlNorum :o) 即使使用了 -Oc 标志,由于你给了提示,我仍然能够发现有用的信息,但我需要编写一个额外的小脚本来提取信息,而且当我更改源代码时它很容易出错。不过还是很好的建议。 - jippie
4个回答

3

GCC有命令行选项,可以在编译的任何阶段请求它转储出中间表示形式。 "树" 转储以伪C语法呈现,并包含您所需的信息。 就您尝试做的事情而言,-fdump-tree-original-fdump-tree-optimized 转储发生在优化流水线的有用点上。 我手边没有AVR编译器,因此我修改了您的测试用例,使其自包含并可用于我手头的编译器:

typedef unsigned short uint16_t;
const int F_CPU = 4000000;
const uint16_t baudrate = 9600;
extern uint16_t UBRRH, UBRRL;

void 
setupUART(void)
{
    uint16_t ubrr = ((F_CPU / (16 * (float) baudrate)) - 1 + .5);
    UBRRH = ubrr >> 8;
    UBRRL = ubrr & 0xff;
}

然后

$ gcc -O2 -S -fdump-tree-original -fdump-tree-optimized test.c
$ cat test.c.003t.original
;; Function setupUART (null)
;; enabled by -tree-original


{
  uint16_t ubrr = 25;

    uint16_t ubrr = 25;
  UBRRH = (uint16_t) ((short unsigned int) ubrr >> 8);
  UBRRL = ubrr & 255;
}

$ cat test.c.149t.optimized
;; Function setupUART (setupUART, funcdef_no=0, decl_uid=1728, cgraph_uid=0)

setupUART ()
{
<bb 2>:
  UBRRH = 0;
  UBRRL = 25;
  return;
}

你可以看到常量表达式折叠是在如此早的时候完成的,以至于它已经出现在“原始”转储中(这是您可以拥有的最早可理解的转储),而优化进一步将移位和掩码操作折叠到写入UBRRH和UBRRL语句中。文件名中的数字(003t和149t)可能与您的不同。如果您想查看所有“树”转储,请使用 -fdump-tree-all 。还有“ RTL” 转储,它们看起来与 C 不一样,可能对您没有用。不过,如果您好奇,-fdump-rtl-all 会打开它们。总共有大约100个树和60个RTL转储,因此最好在临时目录中执行此操作。(嘘:每当您在括号内部放置空格时,上帝就会杀死一只小猫。)

不错啊!我可以执行 grep -E '^[ \ t] * uint16_t ubrr' project.cpp.003t.original,看起来像是一个相当强大的解决方法。 - jippie

2
可能有一种打印中间结果的解决方案,但需要一些时间来实现。因此,只有在源代码规模相当大时才值得尝试。
您可以定制您的GCC编译器;通过插件(用C或C++编写)或通过MELT扩展。MELT是一个高级、类似于Lisp的领域特定语言,用于扩展GCC。(它作为GCC的[元]插件实现,并被翻译成适合GCC的C++代码)。
然而,这种方法要求您了解GCC内部机制,然后添加自己的“优化”通道来进行面向方面编程(例如使用MELT)以打印相关的中间结果。
您也可以查看生成的汇编代码(并使用-fverbose-asm -S选项),还可以查看生成的Gimple表示(也许可以使用-fdump-tree-gimple)。对于一些交互式工具,请考虑图形MELT探针
也许添加自己的内建函数(使用MELT扩展)像__builtin_display_compile_time_constant可能是相关的。

1
我怀疑没有简单的方法来确定编译器的操作。在gcc中可能有一些工具可以转储语言的中间形式,但它肯定不容易阅读,除非你真的怀疑编译器正在做错什么(并且有一个非常小的示例来展示它),否则你不太可能用它来做任何有意义的事情——因为跟踪正在发生的事情需要太多的工作。
如果您担心代码正确性,则更好的方法是添加临时变量(以及可能的打印)。
    uint16_t ubrr = ( ( F_CPU / ( 16 * (float) baudrate ) ) - 1 + .5 );
    uint8_t ubrr_high = ubrr >> 8
    uint8_t ubrr_low = ubrr & 0xff;
    UBRRH = ubrr_high;
    UBRRL = ubrr_low;

现在,如果您有一个未经优化的构建,并在GDB中逐步执行它,您应该能够看到它的执行情况。否则,可以向代码添加某种打印输出以显示值是什么... 如果由于正在设置用于打印的uart而无法在目标系统上打印它,则可以在本地主机系统上复制代码并进行调试。除非编译器非常有缺陷,否则从相同的编译中应该获得相同的值。

1

这里有一个技巧:只需要将你现在手动操作的事情自动化。

  • 在你的makefile中,确保avr-gcc生成反汇编代码(-ahlms=output.lst)。或者,将你自己的反汇编方法作为后编译步骤在makefile中使用。
  • 作为后编译步骤,使用你喜欢的脚本语言处理你的列表文件,查找out UBRRHout UBRRL行。这些值将从寄存器中加载,因此你的脚本可以提取将要加载到UBRRHUBRRL中的寄存器的前一个赋值。然后,脚本可以从通用寄存器中加载的值重新组装UBRR值,这些寄存器用于设置UBRRHUBRRL
这似乎比Basile Starynkevich的非常有用的MELT扩展更容易。现在,首先看起来这个解决方案似乎脆弱,所以让我们考虑一下这个问题:
  • 我们知道(至少在您的处理器上),指令out UBRR_, r__将出现在反汇编列表中:没有其他设置寄存器/写入端口的方法。可能会发生的一件事是这些行周围的间距发生变化,但是您的脚本可以轻松处理。
  • 我们还知道out指令只能从通用寄存器中执行,因此我们知道第二个参数将是通用寄存器,因此这不应该成为问题。
  • 最后,我们还知道在out指令之前将设置此寄存器。在这里,我们必须允许一些变化:除了使用立即加载(LDI)外,avr-gcc可能会生成其他一些设置寄存器值的指令集。我认为作为第一次尝试,脚本应该能够解析立即加载,并且否则转储涉及将被写入UBRR_端口的寄存器的最后一个指令。
如果更改平台,脚本可能需要更改(某些处理器使用UBRRH1/2寄存器代替UBRRH),但在这种情况下,波特率代码将不得不更改。如果脚本抱怨无法解析反汇编,则至少您会知道未执行检查。

UBRRH会显示r1,通常在启动时设置为0(使用eor r1,r1),但确切地说很难确定。 - jippie
最糟糕的情况是,您的脚本可以使用“打印涉及用于设置端口的寄存器的最后一件事”的启发式方法来打印eor r1,r1。或者您可以在脚本中编写一个小型AVR模拟器,教它将一个值与自身进行异或运算意味着“将其设置为零” :) - angelatlarge

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