我正在编写一个脚本,该脚本除其他功能外,对函数的参数进行读取并进行一些处理。现在,我在addr上查找给定函数的参数数量的解决方案是:

(GetStrucSize(GetFrame(addr)) - GetFrameSize(addr))/4


这是在x86上。问题是当我调用printf()时,两个GetXXX()调用之间的差为5,这显然弄乱了数学。

目前,我通过检查是否第一个参数是一个格式字符串与格式说明,然后手动解析它才能获得真正的计数,但不是真正的可扩展性。

有没有在API的限制的方式来解决这个问题?

编辑:我应该在问题中更清楚地说明这一点,但这是专门指IDA API。我可以自己编写代码以计算清除量,但是为了保持代码的整洁度,我试图避免查询很多代码(如果是stdcall和cdecl),然后搜索/解析自己

#1 楼

对于cdecl,调用者需要清理堆栈。因此,在函数调用之后的某处,必须有一个从堆栈中删除参数的堆栈操作(如在调整堆栈指针中删除一样)

PUSH 0xABCDEF01
CALL function
ADD ESP, 4 ; sizeof(DWORD) (the parameter)


它不需要直接在通话后。 (我认为VS08有时会将返回值检查放在它前面)
所以这同样有效。

PUSH 0xABCDEF01
CALL function
TEST EAX, EAX
ADD ESP, 4 ; sizeof(DWORD) (the parameter)
; A conditional jump probably


因为这是

PUSH 0xABCDEF01
CALL function
POP EAX 


因为EAX持有DWORD,因此从堆栈中删除了sizeof(DWORD) == 4个字节。 (这显然对于所有其他通用扩展寄存器也适用)。
(可能对于N参数来说,可以使用N POP,但是我敢肯定没有编译器会这样做,并且对于每个N> 2,将会导致更大的代码,因此在手写汇编中也将是不现实的。)

不存在最大N条指令,您可以在调用后检查以确定该堆栈操作是否为清理代码。但是我认为5几乎适用于所有情况(如果您偶然知道此函数是CDECL,那么请检查所需的数目)

也适用于返回值不是的函数重要但在检查另一个函数的返回值之前被调用,可以保存一个这样的变量:

CALL function1
PUSH EAX ; Save EAX
PUSH something
CALL function2
POP EAX ; Remove the something parameter
POP EAX ; Restore the return value of function1


您也可以尝试计算PUSH,但这会带有自己的中间指令-这是很常见的:

PUSH something
LEA EAX, something_else ; Get a pointer to something_else
PUSH EAX 
PUSH more
CALL function


因此,一旦遇到非标准指令,您就无法停止工作。 PUSH

总而言之,没有完美的解决方案,但我认为两者都可以带来相当可观的价值。您选择哪个(或同时选择两个?)取决于您,IMO第一个(搜索堆栈清理)更容易实现。

评论


感谢你的回答;我可能会接受这一解决方案。我理解cdecl的调用约定,但希望已经有一种实现此方法的方法:(

–Fewmitz
2014年9月4日在18:08

@Fewmitz我确定有人可以为这些技术提供适当的名称,这可能会导致最终实现。我认为这是一项相当普通的任务,所以某人一定已经做到了

–user45891
2014年9月4日在18:11

是的,我应该更清楚地指出这与API有关;我可以自己编写它,但尝试保持代码整洁,尤其是在使用不同的体系结构等时。

–Fewmitz
2014年9月4日在18:15

Trivia factoid,我已经看到编译器优化了连续的esp清理-当在一个序列中调用两个cdecl函数时,编译器(VC6)在第一次调用之后没有执行清理,只有在第二次调用之后才执行。

– DCoder
2014年9月4日19:12



@DCoder很好。我做了一个快速测试,当启用任何类型的(速度或大小)优化时,VS2008仍然会这样做(启用链接时代码生成-我不确定那种情况下的模块是否也意味着printf())。

–user45891
2014年9月4日19:31