我正在尝试使用静态分析来计算嵌入式程序的最大堆栈使用率。
我已经使用编译器标志-fstack-usage
来获取每个函数的最大堆栈使用量,并使用标志-fdump-rtl-expand
来生成所有函数调用的图形。
最后一个缺少的成分是内置函数的堆栈用法。 (目前只有memset
)
我想我可以用其他方法来测量它,并在脚本中添加一个常量。但是,我不希望在新版本的GCC中更改内置函数的实现并且脚本中的值保持不变。
也许有某种方法可以用标志-fstack-usage
编译内置函数?还是通过静态分析来衡量其堆栈使用率的其他方法?
编辑:
此问题不是Stack Size Estimation的重复项。另一个问题是关于估计整个程序的堆栈使用率,而我问如何为单个内置库函数估计堆栈使用率。另一个问题甚至没有提到内置库函数,也没有提及任何答案。
答案 0 :(得分:1)
方法1(动态分析)
您可以在运行时确定堆栈大小,方法是使用预定义的模式填充堆栈,执行memset
,然后检查已修改了多少个字节。由于您需要编译示例程序,将其上载到目标(除非您有模拟器)并收集结果,所以这比较慢,而且涉及更多。您还需要注意提供给函数的测试数据,因为执行路径可能会根据大小,数据对齐方式等而改变。
有关此方法的真实示例,请检查Abseil's code。
方法2(静态分析)
通常,对二进制代码进行静态分析比较棘手(即使分解起来也不是一件容易的事),并且您需要复杂的符号执行机制来处理它(例如miasm)。但是在大多数情况下,您可以放心地依靠检测编译器用来分配帧的模式。例如。对于x86_64 GCC,您可以执行以下操作:
objdump -d /lib64/libc.so.6 | sed -ne '/<__memset_x86_64>:/,/^$/p' > memset.d
NUM_PUSHES=$(grep -c pushq memset.d)
LOCALS=$(sed -ne '/sub .*%rsp/{ s/.*sub \+\$\([^,]\+\),%rsp.*/\1/; p }' memset.d)
LOCALS=$(printf '%d' $LOCALS) # Unhex
echo $(( LOCALS + 8 * NUM_PUSHES ))
请注意,此简单方法会产生保守估计(可以得到更精确的结果,但需要进行路径敏感的分析,需要进行适当的分析,构建控制流图等),并且确实无法处理嵌套函数调用(可以轻松添加,但应该使用比Shell更具表达能力的语言来完成)。
AVR组装通常更复杂,因为您无法轻松地检测局部变量的空间分配(修改堆栈指针的操作分为几条in
,out
和adiw
指令因此需要在Python中进行非平凡的解析)。像memset
或memcpy
这样的简单函数不使用局部变量,因此您仍然可以摆脱简单的困惑:
NUM_PUSHES=$(grep -c 'push ' memset.d)
NUM_RCALLS=$(grep -c 'rcall \+\.+0' memset.d)
# A safety check for functions which we can't handle
if grep -qi 'out \+0x3[de]' memset.d; then
echo >&2 'Unable to parse stack modification'
exit 1
fi
echo $((NUM_PUSHES + 2 * NUM_RCALLS))
答案 1 :(得分:0)
这不是一个很好的答案,但仍然可能有用。
许多内置函数非常简单。例如,memset
可以作为一个简单的循环来实现。从我的观察看来,如果编译器只能使用寄存器(这是很合理的话),则避免使用堆栈。只有很长的函数需要更多的堆栈。短些需要的只是ret
指令的返回地址。
假设简单的内置函数除了指令call
和ret
之外根本不使用堆栈,是相对安全的,因此内存量等于指向a的指针的大小。功能。 (以我的情况为2个字节)
请记住,嵌入式系统并不总是具有冯·诺依曼体系结构,它们通常将指令和数据存储在单独的存储器中。函数和数据的指针大小可能不同。