方法一(动态分析)
您可以通过使用预定义的模式填充堆栈、执行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 中进行非平凡的解析)。像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))