如何估计库函数的使用

问题描述 投票:0回答:2

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

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

最后一个缺失的成分是内置函数的堆栈使用。 (目前它只是memset

我想我可以通过其他方式测量它并在我的脚本中加入常量。但是,我不希望在新版本的GCC中内置函数的实现发生更改并且我的脚本中的值保持不变。

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


编辑:

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

c++ gcc g++ static-analysis gcc8
2个回答
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程序集通常更复杂,因为您无法轻松检测到局部变量的空间分配(堆栈指针的修改分为几个inoutadiw指令,因此需要在例如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))

0
投票

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.