如何估计库函数的使用情况
How to estimate usage of library functions
我正在尝试使用静态分析计算嵌入式程序的最大堆栈使用量。
我使用编译器标志 -fstack-usage
获取每个函数的最大堆栈使用率,并使用标志 -fdump-rtl-expand
生成所有函数调用的图表。
最后一个缺失的成分是内置函数的堆栈使用。 (目前只有memset
)
我想我可以用其他方式测量它并在我的脚本中加入一个常量。但是,我不希望在新版本的 GCC 中内置函数的实现发生变化,而我的脚本中的值保持不变。
也许有一些方法可以编译带有标志 -fstack-usage
的内置函数?或者其他一些通过静态分析来衡量他们的堆栈使用情况的方法?
编辑:
此问题与 Stack Size Estimation 不重复。另一个问题是关于估计整个程序的堆栈使用情况,而我问的是如何为单个 built-in 库函数估计它。另一个问题甚至没有提到 built-in 库函数,也没有提到它的任何答案。
方法一(动态分析)
您可以通过使用预定义模式填充堆栈、执行 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.*//; p }' memset.d)
LOCALS=$(printf '%d' $LOCALS) # Unhex
echo $(( LOCALS + 8 * NUM_PUSHES ))
请注意,这种简单的方法会产生保守估计(获得更精确的结果是可行的,但需要进行路径敏感分析,这需要适当的解析、构建控制流图,等)并且不处理嵌套函数调用(可以很容易地添加,但应该使用比 shell 更具表现力的语言来完成)。
AVR 程序集通常更复杂,因为您无法轻易检测到局部变量 space 的分配(堆栈指针的修改分为几个 in
、out
和adiw
指令因此需要非平凡的解析,例如 Python)。 memset
或 memcpy
等简单函数不使用局部变量,因此您仍然可以使用简单的 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))
这不是一个很好的答案,但它可能仍然有用。
很多内置函数都非常简单。例如 memset
可以作为一个简单的循环来实现。根据我的观察,如果编译器只能使用寄存器(这很有意义),它似乎会避免使用堆栈。只有很长的函数才需要更多的堆栈。所有较短的需要的是 ret
指令的 return 地址。
假设简单的内置函数除了指令 call
和 ret
之外根本不使用堆栈是相对安全的,因此内存量等于指针的大小到一个函数。 (在我的例子中是 2 个字节)
请记住,嵌入式系统并不总是采用冯·诺依曼架构,它们通常将指令和数据存储在不同的存储器中。指向函数和数据的指针的大小可能不同。
我正在尝试使用静态分析计算嵌入式程序的最大堆栈使用量。
我使用编译器标志 -fstack-usage
获取每个函数的最大堆栈使用率,并使用标志 -fdump-rtl-expand
生成所有函数调用的图表。
最后一个缺失的成分是内置函数的堆栈使用。 (目前只有memset
)
我想我可以用其他方式测量它并在我的脚本中加入一个常量。但是,我不希望在新版本的 GCC 中内置函数的实现发生变化,而我的脚本中的值保持不变。
也许有一些方法可以编译带有标志 -fstack-usage
的内置函数?或者其他一些通过静态分析来衡量他们的堆栈使用情况的方法?
编辑:
此问题与 Stack Size Estimation 不重复。另一个问题是关于估计整个程序的堆栈使用情况,而我问的是如何为单个 built-in 库函数估计它。另一个问题甚至没有提到 built-in 库函数,也没有提到它的任何答案。
方法一(动态分析)
您可以通过使用预定义模式填充堆栈、执行 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.*//; p }' memset.d)
LOCALS=$(printf '%d' $LOCALS) # Unhex
echo $(( LOCALS + 8 * NUM_PUSHES ))
请注意,这种简单的方法会产生保守估计(获得更精确的结果是可行的,但需要进行路径敏感分析,这需要适当的解析、构建控制流图,等)并且不处理嵌套函数调用(可以很容易地添加,但应该使用比 shell 更具表现力的语言来完成)。
AVR 程序集通常更复杂,因为您无法轻易检测到局部变量 space 的分配(堆栈指针的修改分为几个 in
、out
和adiw
指令因此需要非平凡的解析,例如 Python)。 memset
或 memcpy
等简单函数不使用局部变量,因此您仍然可以使用简单的 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))
这不是一个很好的答案,但它可能仍然有用。
很多内置函数都非常简单。例如 memset
可以作为一个简单的循环来实现。根据我的观察,如果编译器只能使用寄存器(这很有意义),它似乎会避免使用堆栈。只有很长的函数才需要更多的堆栈。所有较短的需要的是 ret
指令的 return 地址。
假设简单的内置函数除了指令 call
和 ret
之外根本不使用堆栈是相对安全的,因此内存量等于指针的大小到一个函数。 (在我的例子中是 2 个字节)
请记住,嵌入式系统并不总是采用冯·诺依曼架构,它们通常将指令和数据存储在不同的存储器中。指向函数和数据的指针的大小可能不同。