50

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

我被告知 gcc 编译器现在有一个 -fstack-usage 选项和 -fcallgraph-info 选项,它们可以以某种方式用于为我静态计算确切的“最大堆栈使用量”。(“使用 GCC 进行编译时堆栈需求分析”,Botcazou、Comar 和 Hainque 着)。

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

另外,我确保我的中断处理程序在它们最后的从中断返回指令之前都不会重新启用中断,所以我不需要担心可重入中断处理程序。

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

我正在使用带有 gcc 4.6.0 的 YAARTO 来编译 LM3S1968 ARM Cortex-M3 的代码。

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

(有关针对 Keil 编译器的几乎相同的问题,请参阅如何确定嵌入式系统中的最大堆栈使用量。)

4

7 回答 7

26

海合会文档:

-fstack 用法

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

  • 函数的名称。
  • 字节数。
  • 一个或多个限定符:静态、动态、有界。

限定符 static 意味着函数静态地操作堆栈:在函数进入时为帧分配固定数量的字节,并在函数退出时释放;没有在函数中进行堆栈调整。第二个字段是这个固定的字节数。

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

我找不到任何对 -fcallgraph-info 的引用

您可能会从 -fstack-usage 和 -fdump-tree-optimized 创建所需的信息

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

于 2011-06-17T19:44:49.770 回答
13

以防万一没有人提出更好的答案,即使我没有使用这些选项和工具的经验,我也会将我在评论中的内容发布到您的另一个问题:

GCC 4.6 添加了一个-fstack-usage选项,该选项可以逐个函数地提供堆栈使用统计信息。

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

因此,看起来这可以使用 GCC 完成,但您可能必须拼凑正确的工具集。

于 2011-06-17T21:44:46.297 回答
8

很晚了,但是对于任何看到这个的人来说,给出的答案涉及结合 fstack-usage 和调用图工具(如 cflow)的输出,对于任何动态分配,甚至是有界的,最终都可能完全不正确,因为没有关于何时该动态堆栈的信息分配发生。因此,不可能知道您应该将该值应用于哪些功能。作为一个人为的例子,如果(简化的)fstack-usage 输出是:

main        1024     dynamic,bounded
functionA    512     static
functionB     16     static

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

main
    functionA
    functionB

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

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

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

于 2014-09-27T21:10:33.047 回答
6

我最终编写了一个 python 脚本来实现 τεκ 的答案。代码太多在这里贴,但是可以在github上找到

于 2016-05-20T17:07:20.917 回答
4

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

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

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

正如这里所讨论的,使用这里的 Perl 脚本,已经为您完成了创建这样一个工具的工作,这是很有帮助的。

于 2021-05-05T22:31:37.410 回答
2

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

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

运行时:在每个函数调用之前和之后检查当前堆栈使用情况。用 编译代码-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并将其添加到结果中。

于 2021-05-05T09:08:45.323 回答