如何估算库函数的使用

时间:2019-04-19 18:23:08

标签: c++ gcc g++ static-analysis gcc8

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

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

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

我想我可以用其他方法来测量它,并在脚本中添加一个常量。但是,我不希望在新版本的GCC中更改内置函数的实现并且脚本中的值保持不变。

也许有某种方法可以用标志-fstack-usage编译内置函数?还是通过静态分析来衡量其堆栈使用率的其他方法?


编辑:

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

2 个答案:

答案 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组装通常更复杂,因为您无法轻松地检测局部变量的空间分配(修改堆栈指针的操作分为几条inoutadiw指令因此需要在Python中进行非平凡的解析)。像memsetmemcpy这样的简单函数不使用局部变量,因此您仍然可以摆脱简单的困惑:

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指令的返回地址。

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

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