0

我正在尝试使用静态分析计算嵌入式程序的最大堆栈使用量。

我使用编译器标志-fstack-usage来获取每个函数的最大堆栈使用量,并使用该标志-fdump-rtl-expand来生成所有函数调用的图表。

最后缺少的成分是内置函数的堆栈使用。(目前只有memset

我想我可以用其他方式测量它并将一个常数放入我的脚本中。但是,我不希望在新版本的 GCC 中内置函数的实现发生变化,而我的脚本中的值保持不变。

也许有一些方法可以用 flag 编译内置函数-fstack-usage?或者其他方式通过静态分析来衡量他们的堆栈使用情况?


编辑:

这个问题不是Stack Size Estimation的重复问题。另一个问题是关于估计整个程序的堆栈使用情况,而我询问如何估计单个内置库函数的堆栈使用情况。另一个问题甚至没有提到内置库函数,也没有提到它的任何答案。

4

2 回答 2

1

方法一(动态分析)

您可以通过使用预定义的模式填充堆栈、执行memset然后检查已修改的字节数来确定运行时的堆栈大小。由于您需要编译示例程序,将其上传到目标(除非您有模拟器)并收集结果,因此这会更慢且涉及更多。您还需要注意提供给函数的测试数据,因为执行路径可能会根据大小、数据对齐等而改变。

有关此方法的真实示例,请查看Abseil 的代码

方法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 中进行非平凡的解析)。像memsetmemcpy不使用局部变量这样的简单函数,因此您仍然可以使用简单的 greps:

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))
于 2019-04-22T18:40:56.187 回答
0

这不是一个很好的答案,但它仍然可能有用。

许多内置函数非常简单。例如memset可以实现为一个简单的循环。根据我的观察,如果编译器只能使用寄存器(这很有意义),它似乎会避免使用堆栈。只有很长的函数需要更多的堆栈。所有较短的需要的是ret指令的返回地址。

假设简单的内置函数除了指令call和之外根本不使用堆栈是相对安全的ret,因此内存量等于函数指针的大小。(在我的情况下为 2 个字节)

请记住,嵌入式系统并不总是具有冯诺依曼架构,它们通常将指令和数据存储在单独的存储器中。指向函数和数据的指针的大小可能不同。

于 2019-04-24T09:42:25.440 回答