如何用gcc确定嵌入式系统中的最大堆栈使用量?

时间:2011-06-17 14:51:09

标签: gcc embedded code-analysis static-analysis

我正在为嵌入式系统编写启动代码 - 在跳转到main()函数之前加载初始堆栈指针的代码 - 我需要告诉它我的应用程序将使用多少字节的堆栈(或者更大的保守估计。)

我被告知gcc编译器现在有一个-fstack-usage选项和-fcallgraph-info选项,可以某种方式用于静态计算我的“最大堆栈使用率”。 (Botcazou,Comar和Hainque的"Compile-time stack requirements analysis with GCC")。

Nigel Jones说嵌入式系统中的递归是一个非常糟糕的主意(“计算你的堆栈大小”2009),所以我一直小心不要在这段代码中做任何相互递归的函数。

另外,我确保我的中断处理程序都没有重新启用中断,直到它们最终从中断返回指令,所以我不需要担心重入中断处理程序。

如果没有递归或重入中断处理程序,应该可以静态地确定最大堆栈使用情况。 (所以How to determine maximum stack usage?的大部分答案都不适用)。 我的理解是我(或者最好是我的PC上的一些代码在每次重建可执行文件时自动运行)首先找到每个中断处理程序的最大堆栈深度,当它没有被更高优先级的中断中断时,最大值当main()函数没有被中断时,它的堆栈深度。 然后我将它们全部添加到找到总(最坏情况)最大堆栈深度。当main()后台任务在被最低优先级中断中断时处于最大深度时发生(在我的嵌入式系统中),并且当中断被下一个最低优先级中断时,该中断处于其最大深度中断,等等。

我正在使用YAGARTO和gcc 4.6.0来编译LM3S1968 ARM Cortex-M3的代码。

那么如何使用-fstack-usage选项和-fcallgraph-info选项与gcc一起计算最大堆栈深度?或者是否有更好的方法来确定最大堆栈使用量?

(请参阅How to determine maximum stack usage in embedded system?了解针对Keil编译器的几乎相同的问题。)

7 个答案:

答案 0 :(得分:20)

GCC docs:

  

-fstack使用率

     

基于每个函数为程序生成编译器输出堆栈使用信息。转储的文件名是通过将.su附加到auxname来实现的。 auxname是从输出文件的名称生成的,如果明确指定并且它不是可执行文件,否则它是源文件的基本名称。条目由三个字段组成:

     
      
  • 该功能的名称。
  •   
  • 多个字节。
  •   
  • 一个或多个限定符:静态,动态,有界。
  •   
     

限定符static表示该函数静态操作堆栈:在函数入口处为帧分配固定数量的字节,并在函数出口处释放;否则在功能中不进行堆栈调整。第二个字段是固定的字节数。

     

限定符动态意味着函数动态地操作堆栈:除了上面描述的静态分配之外,还在函数体中进行堆栈调整,例如围绕函数调用推送/弹出参数。如果限定条件也存在,则这些调整的数量在编译时受限,第二个字段是函数使用的堆栈总量的上限。如果不存在,则这些调整的数量在编译时不受限制,第二个字段仅表示有界部分。

我找不到对-fcallgraph-info

的任何引用

您可以从-fstack-usage和-fdump-tree-optimized

创建所需的信息。

对于-fdump-tree-optimized中的每个叶子,获取其父项并将它们的堆栈大小数加起来(请记住,这个数字适用于任何具有“动态”但不是“有界”的函数)来自-fstack-usage,找到这些值的最大值,这应该是您的最大堆栈使用量。

答案 1 :(得分:12)

如果没有人提出更好的答案,我会将评论中的内容发布到您的其他问题,即使我没有使用这些选项和工具的经验:

GCC 4.6添加了-fstack-usage选项,该选项在逐个函数的基础上提供堆栈使用情况统计信息。

如果您将此信息与cflow或类似工具生成的调用图结合使用,您可以获得您正在寻找的堆栈深度分析(可能很容易编写脚本来执行此操作) 。让脚本读取堆栈使用信息,并使用函数使用的堆栈加载函数名称的映射。然后让脚本遍历cflow图形(可以是一个易于解析的文本树),将调用图中每个分支的每一行相关的堆栈使用量相加。

所以,看起来这可以通过GCC来完成,但你可能需要拼凑正确的工具集。

答案 2 :(得分:5)

很晚,但是对于任何看过这个问题的人来说,将fstack-usage和cflow等调用图形工具的输出结合起来的答案最终可能会对任何动态分配造成严重错误,甚至是有限的,因为没有关于何时的信息发生动态堆栈分配。因此,不可能知道应该将值应用于哪些函数。作为一个人为的例子,如果(简化)fstack-usage输出是:

main        1024     dynamic,bounded
functionA    512     static
functionB     16     static

和一个非常简单的调用树是:

main
    functionA
    functionB

将这些结合起来的天真方法可能会导致主要 - > functionA被选为1536字节的最大堆栈使用路径。但是,如果main()中最大的动态堆栈分配是在调用functionB的条件块中直接在堆栈上将一个像记录这样的大型参数推送到函数B()(我已经说过这是设计的),那么真正的主要 - &gt ; functionB是最大堆栈使用的路径,为1040字节。根据现有的软件设计,以及传递堆栈中所有内容的其他更受限制的目标,累积错误可能会迅速导致您查看声称显着夸大的最大堆栈大小的完全错误的路径。

此外,根据您在谈论中断时“重入”的分类,可能会完全错过一些堆栈分配。例如,许多Coldfire处理器的7级中断是边沿敏感的,因此忽略了中断禁用掩码,因此如果使用信号量提前离开指令,您可能不会认为它是可重入的,但是初始堆栈分配仍然会发生之前检查信号量。

简而言之,您必须非常小心使用这种方法。

答案 3 :(得分:5)

我最终编写了一个python脚本来实现τεκ的answer。这里发布的代码太多了,但可以在github

上找到

答案 4 :(得分:3)

我不熟悉-fstack-usage-fcallgraph-info选项。但是,始终可以通过以下方式确定实际堆栈使用情况:

  1. 分配足够的堆栈空间(对于此实验),并将其初始化为易于识别的内容。我喜欢0xee
  2. 运行应用程序并测试其所有内部路径(通过输入和参数的所有组合)。让它运行超过“足够长”。
  3. 检查堆栈区域并查看堆栈的使用量。
  4. 使堆栈大小加10%或20%以容忍软件更新和罕见情况。

答案 5 :(得分:2)

通常有两种方法 - 静态和运行时。

静态:使用 -fdump-rtl-expand -fstack-usage 编译您的项目,并从 *.expand 脚本获取每个函数的调用树和堆栈使用情况。然后遍历调用树中的所有叶子并计算每个叶子中的堆栈使用量并获得最高的堆栈使用量。然后将该值与目标上的可用内存进行比较。这是静态工作的,不需要运行程序。这不适用于递归函数。不适用于 VLA 阵列。如果 sbrk() 在链接器部分而不是静态预分配缓冲区上操作,则它不会考虑动态分配,动态分配可能会从另一侧自行增长。我的树 https://docs.python.org/3/library/operator.html 中有一个脚本,我用它来探索这个选项。

运行时:在每次函数调用之前和之后检查当前堆栈使用情况。使用 -finstrument-functions 编译代码。然后在您的代码中定义两个函数,大致应该获取当前堆栈使用情况并对其进行操作:

static unsigned long long max_stack_usage = 0;

void __cyg_profile_func_enter(void * this, void * call) __attribute__((no_instrument_function)) {
      // get current stack usage using some hardware facility or intrisic function
      // like __get_SP() on ARM with CMSIS
      unsigned cur_stack_usage = __GET_CURRENT_STACK_POINTER() - __GET_BEGINNING_OF_STACK();
      // use debugger to output current stack pointer
      // for example semihosting on ARM
      __DEBUGGER_TRANSFER_STACK_POINTER(cur_stack_usage);
      // or you could store the max somewhere
      // then just run the program
      if (max_stack_usage < cur_stack_usage) {
            max_stack_usage = max_stack_usage;
      }
      // you could also manually inspect with debugger
      unsigned long long somelimit = 0x2000;
      if (cur_stack_usage > somelimit) {
           __BREAKPOINT();
      }
}
void __cyg_profile_func_exit(void * this, void * call) __attribute__((no_instrument_function)) {
      // well, nothing
}

在执行每个函数之前和之后 - 您可以检查当前堆栈使用情况。因为函数是在函数内使用堆栈之前调用的,所以此方法不采用当前函数堆栈的使用情况 - 这只是一个函数,并没有做太多事情,并且可以通过获取它是哪个函数然后获取堆栈来以某种方式减轻与 -fstack-usage 一起使用并将其添加到结果中。

答案 6 :(得分:2)

通常,您需要将调用图信息与 -fstack-usage 生成的 .su 文件结合起来,以找到从特定函数开始的最深堆栈使用情况。从 main() 或线程入口点开始,将为您提供该线程的最坏情况。

使用 here 中的 Perl 脚本,如here 所讨论的那样,已经为您完成了创建此类工具的工作。