#stack #size #space #estimate #requirements #programs #thumb2

app thumb2-stack-size

估算Thumb/Thumb2程序的堆栈空间需求

2个版本

使用旧的Rust 2015

0.1.1 2018年4月8日
0.1.0 2018年4月8日

#598 in 嵌入式开发

MIT许可证

87KB
1.5K SLoC

thumb2-stack-size

估算Thumb/Thumb2二进制堆栈使用的工具。

微控制器开发通常需要显式管理堆栈。大多数微控制器没有虚拟寻址能力,因此它们的堆栈必须在事先完全预分配。它们也大多只有少量RAM,因此微控制器的堆栈通常很小。这带来了一点困难:必须分配足够的堆栈空间以确保不会发生越界,但堆栈上应该尽可能少地浪费内存。

此工具可以估算给定Thumb或Thumb2二进制文件使用的堆栈空间量。

先决条件

目前,thumb2-stack-size仅支持32位静态链接ARM可执行ELF文件。ELF文件必须包含符号信息,调试信息不是必需的。符号表用于从输入二进制文件中重建函数。

估算算法

读取输入文件并将其拆分为函数,使用符号表定位函数及其大小。然后逐条指令解析每个函数,此时是一个指令流。如果遇到ARM指令集未定义的指令,则引发错误并终止估算。("定义"包括永久未定义的指令udfudf.w).

结果函数通过虚拟机运行,该虚拟机跟踪每个指令执行的堆栈操作。堆栈操作可以是静态的(例如,push、pop、一次性保留大量堆栈空间)或动态的(例如,alloca)。跟踪也可以是静态的或动态的,但也可以是发散的:如果两个跟踪导致相同的指令,但以不同的堆栈指针值到达该指令,则无法完全分析该函数。该工具将尝试最佳分析并计算函数堆栈需求的下限。

模拟函数的所有可能跟踪,以给出给定函数的三个可能结果

  • 堆栈使用完全静态。在这种情况下,估算结果准确。
  • 堆栈使用是动态的,但不是证明发散的。这通常是由于代码中的间接分支引起的,例如通过调用函数指针或特质对象的方法。
  • 堆栈使用是可证明发散的。相互递归的函数可能是这种结果的原因,或者函数在循环中调用 alloca

这个工具值得注意的是不支持对表格分支的分析(即 tbbtbh 指令)。表格分支通常由 Rust 格式化代码发出。每当发现此类分支时,都会发出警告。可以使用 -C llvm-args=-min-jump-table=99999 重新编译二进制文件并重新分析;此处将 -min-jump-table= 选项设置得非常高,使得 LLVM 不允许发出表格分支。生成的二进制文件在功能上与具有相同堆栈属性的二进制文件相同,但可能更大。

输出格式

在不带选项的情况下运行时,工具将输出所有警告,然后是一个表格,其中包含每个函数的一行。该表格列出

  • 函数的地址
  • 该函数的堆栈需求
  • 该函数是静态的、动态的还是发散的
  • Rust 解包后的函数名称,如果解包失败,则是原始符号名称

每个警告都附有导致警告的符号(解包后)的名称和地址。地址有助于区分具有相同解包名称的函数,这在使用不同类型签名的泛型函数中很常见。

包含间接分支(在这种情况下是通过函数指针调用)的小程序可能会产生以下输出

warning: function __reset @ 8000040 cannot be statically analyzed
 * indirect branches found
warning: function hcl::timer::TimerSet::tick @ 80001cc cannot be statically analyzed
 * indirect branches found
warning: function timer::systick @ 8000080 cannot be statically analyzed
 * calls to dynamic functions found: 80001cc,

 8000040  0 dynamic            __reset
 8000080  48 dynamic           timer::systick
 8000084  128 static           timer::main
 8000134  136 static           __hcl_entry
 800013e  0 static             hcl::qlist::QlistData::unlink
 800015c  0 static             hcl::qlist::QlistData::pop_front
 8000168  0 static             <hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before
 80001cc  48 dynamic           hcl::timer::TimerSet::tick
 800023c  32 static            hcl::timer::TimerSet::add_stub
 8000272  0 static             hcl::timer::invoke_indirect
 8000296  16 static            hcl::timer::invoke_indirect
 80002b6  32 static            hcl::timer::invoke_indirect

除了表格外,工具还可以输出所有函数的调用图。调用图可用于追踪间接函数调用,如果开发人员对某些函数有知识,而这些知识对估计算法不可用。

调用图被分成换行符分隔的部分,每一部分对应输入中的一个函数。每个函数按深度优先顺序遍历,在此过程中递归地列出该函数的所有被调用者。相互递归的函数也会打印出来,但调用图在图中第一次出现两次的第一个函数处被省略号标记截断。

调用图的每一行都包含被调用函数的地址,从打印调用图的函数开始。对于每个函数,都会打印分析结果,然后是函数名称。

与上述相同的二进制文件产生以下调用图

 8000040  0 dynamic             __reset

 8000080  48 dynamic            timer::systick
 80001cc  48 dynamic             └ hcl::timer::TimerSet::tick
 800015c  0 statichcl::qlist::QlistData::pop_front
 800013e  0 static                 │ └ hcl::qlist::QlistData::unlink
 8000168  0 static<hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 8000084  128 static            timer::main
 800023c  32 statichcl::timer::TimerSet::add_stub
 8000168  0 static<hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 8000134  136 static            __hcl_entry
 8000084  128 statictimer::main
 800023c  32 statichcl::timer::TimerSet::add_stub
 8000168  0 static<hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 800013e  0 static              hcl::qlist::QlistData::unlink

 800015c  0 static              hcl::qlist::QlistData::pop_front
 800013e  0 statichcl::qlist::QlistData::unlink

 8000168  0 static              <hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 80001cc  48 dynamic            hcl::timer::TimerSet::tick
 800015c  0 statichcl::qlist::QlistData::pop_front
 800013e  0 static               │ └ hcl::qlist::QlistData::unlink
 8000168  0 static<hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 800023c  32 static             hcl::timer::TimerSet::add_stub
 8000168  0 static<hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 8000272  0 static              hcl::timer::invoke_indirect

 8000296  16 static             hcl::timer::invoke_indirect
 800013e  0 statichcl::qlist::QlistData::unlink

 80002b6  32 static             hcl::timer::invoke_indirect
 800013e  0 statichcl::qlist::QlistData::unlink
 8000168  0 static<hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before

 8000040  0 dynamic            __reset
 8000080  48 dynamic           timer::systick
 8000084  128 static           timer::main
 8000134  136 static           __hcl_entry
 800013e  0 static             hcl::qlist::QlistData::unlink
 800015c  0 static             hcl::qlist::QlistData::pop_front
 8000168  0 static             <hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before
 80001cc  48 dynamic           hcl::timer::TimerSet::tick
 800023c  32 static            hcl::timer::TimerSet::add_stub
 8000272  0 static             hcl::timer::invoke_indirect
 8000296  16 static            hcl::timer::invoke_indirect
 80002b6  32 static            hcl::timer::invoke_indirect

请注意,hcl::timer::invoke_indirect 出现多次,但地址不同。

此视图可用于推断有关堆栈使用情况的信息。在示例情况下,已知 systick 调用 hcl::timer::TimerSet::tick,而它反过来可能调用 hcl::timer::invoke_indirect 中的任何函数。在此情况下,tick 至少使用 48 字节,加上最多 32 字节用于任何 invoke_indirect 函数。因此,我们可以推断 tick 不需要超过 80 字节的堆栈空间。

依赖项

约 230KB