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

4

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

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

于 2013-04-15T19:09:52.227 回答
2

可能有打印中间结果的解决方案,但需要一些时间来实施。因此,仅对于相当大的源代码库才值得。

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

然而,这种方法需要您了解 GCC 内部,然后添加您自己的“优化”通道来进行面向方面的编程(例如使用 MELT)以打印相关的中间结果。

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

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

于 2013-04-15T07:39:27.440 回答
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 而无法在目标系统上打印它,则在本地主机系统上复制代码并在那里进行调试。除非编译器有很多错误,否则您应该从相同的编译中获得相同的值。

于 2013-04-15T07:41:59.857 回答
1

这是一个技巧:只需自动化您现在手动执行的操作。

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

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

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

如果您更改平台,脚本可能必须更改(某些处理器有UBRRH1/2寄存器 instea ofUBRRH​​ ,但是在这种情况下,您的波特代码将不得不更改。如果脚本抱怨它无法解析反汇编,那么您至少会知道您的检查尚未执行。

于 2013-04-15T18:30:27.957 回答