2个版本
使用旧的Rust 2015
0.1.1 | 2018年4月8日 |
---|---|
0.1.0 | 2018年4月8日 |
#598 in 嵌入式开发
87KB
1.5K SLoC
thumb2-stack-size
估算Thumb/Thumb2二进制堆栈使用的工具。
微控制器开发通常需要显式管理堆栈。大多数微控制器没有虚拟寻址能力,因此它们的堆栈必须在事先完全预分配。它们也大多只有少量RAM,因此微控制器的堆栈通常很小。这带来了一点困难:必须分配足够的堆栈空间以确保不会发生越界,但堆栈上应该尽可能少地浪费内存。
此工具可以估算给定Thumb或Thumb2二进制文件使用的堆栈空间量。
先决条件
目前,thumb2-stack-size仅支持32位静态链接ARM可执行ELF文件。ELF文件必须包含符号信息,调试信息不是必需的。符号表用于从输入二进制文件中重建函数。
估算算法
读取输入文件并将其拆分为函数,使用符号表定位函数及其大小。然后逐条指令解析每个函数,此时是一个指令流。如果遇到ARM指令集未定义的指令,则引发错误并终止估算。("定义"包括永久未定义的指令udf
和udf.w
).
结果函数通过虚拟机运行,该虚拟机跟踪每个指令执行的堆栈操作。堆栈操作可以是静态的(例如,push、pop、一次性保留大量堆栈空间)或动态的(例如,alloca)。跟踪也可以是静态的或动态的,但也可以是发散的:如果两个跟踪导致相同的指令,但以不同的堆栈指针值到达该指令,则无法完全分析该函数。该工具将尝试最佳分析并计算函数堆栈需求的下限。
模拟函数的所有可能跟踪,以给出给定函数的三个可能结果
- 堆栈使用完全静态。在这种情况下,估算结果准确。
- 堆栈使用是动态的,但不是证明发散的。这通常是由于代码中的间接分支引起的,例如通过调用函数指针或特质对象的方法。
- 堆栈使用是可证明发散的。相互递归的函数可能是这种结果的原因,或者函数在循环中调用
alloca
。
这个工具值得注意的是不支持对表格分支的分析(即 tbb
和 tbh
指令)。表格分支通常由 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 static ├ hcl::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 static └ hcl::timer::TimerSet::add_stub
8000168 0 static └ <hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before
8000134 136 static __hcl_entry
8000084 128 static └ timer::main
800023c 32 static └ hcl::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 static └ hcl::qlist::QlistData::unlink
8000168 0 static <hcl::qlist::QlistLock<'q, A>>::unsafe_insert_before
80001cc 48 dynamic hcl::timer::TimerSet::tick
800015c 0 static ├ hcl::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 static └ hcl::qlist::QlistData::unlink
80002b6 32 static hcl::timer::invoke_indirect
800013e 0 static ├ hcl::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