【问题标题】:can GCC print out intermediate results?GCC 可以打印出中间结果吗?
【发布时间】:2013-04-15 06:57:02
【问题描述】:

检查下面的代码:

#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 文件中提取信息),所以当我编译代码时它会打印出类似 @ 的行987654326@ 或类似的?这样我就可以对计算和设置进行快速的健全性检查。

【问题讨论】:

  • 你试过-S选项吗?
  • @devnull 那不退出编译器吗?我希望编译器完成这项工作,只需打印出它所做的中间计算。
  • 如果你只是不通过-Os,你可能会得到你想在反汇编中看到的东西。不过,您可能不想以这种方式发货。 ;-)
  • @CarlNorum :o) 即使有了 -Oc 标志,我也发现了有用的信息,因为你给出了提示,但我必须编写一个额外的小脚本来提取信息,它可能很容易中断当我更改源代码时。不过很好。

标签: c++ linux gcc avr-gcc


【解决方案1】:

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 转储,因此最好在临时目录中执行此操作。

(Psssst:每次你在括号里面加上空格,上帝就会杀死一只小猫。)

【讨论】:

  • 不错的一个!我可以做一个grep -E '^[ \t]*uint16_t ubrr' project.cpp.003t.original,这对我来说看起来是一个非常强大的解决方法
【解决方案2】:

可能有打印中间结果的解决方案,但需要一些时间才能实施。所以只有相当大的源代码库才值得。

您可以自定义您的 GCC 编译器;通过插件(痛苦地用 C 或 C++ 编码)或通过 MELT 扩展。 MELT 是一种高级的、类似 Lisp 的特定领域语言,用于扩展 GCC。 (它被实现为 GCC 的 [meta-] 插件,并被翻译成适用于 GCC 的 C++ 代码)。

但是这种方法需要您了解 GCC 内部,然后添加您自己的“优化”传递来执行 aspect oriented programming(例如使用 MELT)来打印相关中间结果。 p>

您不仅可以查看生成的程序集(并使用-fverbose-asm -S 作为 GCC 的选项),还可以查看生成的 Gimple 表示(可能使用-fdump-tree-gimple)。对于一些交互式工具,请考虑图形化的MELT probe

也许添加您自己的内置函数(带有 MELT 扩展),如 __builtin_display_compile_time_constant 可能是相关的。

【讨论】:

    【解决方案3】:

    我怀疑是否有一种简单的方法可以确定编译器的功能。 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,然后在您的本地主机系统上复制代码并在那里调试它。除非编译器有很多错误,否则您应该从相同的编译中获得相同的值。

    【讨论】:

      【解决方案4】:

      这里有一个技巧:只需将您现在手动执行的操作自动化即可。

      • 在您的 makefile 中,确保 avr-gcc 生成反汇编 (-ahlms=output.lst)。或者,使用您自己的反汇编方法作为 makefile 中的后编译步骤。
      • 作为编译后步骤,使用您最喜欢的脚本语言处理您的列表文件以查找 out UBRRHout UBRRL 行。这些将从寄存器中加载,因此您的脚本可以将前面的分配提取到将加载到UBRRHUBRRL 的寄存器中。然后,该脚本可以从加载到用于设置UBRRHUBRRL 的通用寄存器中的值重新组装UBRR 值。

      这听起来比Basile Starynkevich 非常有用的MELT 扩展建议更容易。现在,鉴于这个解决方案似乎很脆弱,乍一看,让我们考虑一下这个问题:

      • 我们知道(至少在您的处理器上)out UBRR_, r__ 行将出现在反汇编列表中:根本没有其他方法可以设置寄存器/将数据写入端口。可能会改变的一件事是这些行中/周围的间距,但这可以通过您的脚本轻松处理
      • 我们也知道out指令只能从通用寄存器中发生,所以我们知道会有一个通用寄存器作为out指令行的第二个参数,所以这不应该是一个问题。
      • 最后,我们也知道这个寄存器会在out指令之前设置。这里我们必须考虑一些可变性:而不是LDI(立即加载),avr-gcc 可能会产生一些其他指令集来设置寄存器值。我认为作为第一遍,脚本应该能够解析立即加载,否则会转储它找到的任何最后一条指令,这些指令涉及将写入UBRR_ 端口的寄存器。

      如果您更改平台,脚本可能必须更改(某些处理器的 UBRRH1/2 寄存器实际上是 UBRRH,但是在这种情况下,您的波特代码将不得不更改。如果脚本抱怨它可以'不解析反汇编,那么你至少会知道你的检查没有被执行。

      【讨论】:

      • UBRRH 将显示 r1,它通常在启动时设置为 0(使用 eor r1,r1,但确实很难确定这一点。
      • 好吧,在最坏的情况下,您的脚本可以使用“打印涉及用于设置端口的寄存器的最后一项”启发式方法打印eor r1,r1。或者你可以在你的脚本中编写一个迷你 AVR 模拟器,告诉它一个值与自身进行异或意味着“将其设置为零”:)
      猜你喜欢
      • 1970-01-01
      • 2018-06-24
      • 1970-01-01
      • 1970-01-01
      • 2020-10-23
      • 2021-02-04
      • 1970-01-01
      • 2023-03-20
      • 2011-01-08
      相关资源
      最近更新 更多