Function 在一个函数调用中传递了多少个参数?

Function 在一个函数调用中传递了多少个参数?,function,assembly,call,reverse-engineering,Function,Assembly,Call,Reverse Engineering,我希望分析调用函数的汇编代码,并为每个“调用”找出传递给函数的参数数量。我假设我无法访问目标函数,而只能访问调用代码。 我仅限于使用GCC编译的代码,以及System V ABI调用约定。 我试着从每个“调用”指令中扫描回来,但没有找到足够好的约定,例如,在哪里停止扫描?使用相同参数的两个后续调用会发生什么情况?。非常感谢您的帮助。转载我的评论作为答复 在优化的代码中,您无法可靠地判断。即使在大多数情况下做好工作也可能需要人工智能。e、 函数在RSI中留下值是因为它是第二个参数,还是只是在计算第

我希望分析调用函数的汇编代码,并为每个“调用”找出传递给函数的参数数量。我假设我无法访问目标函数,而只能访问调用代码。 我仅限于使用GCC编译的代码,以及System V ABI调用约定。
我试着从每个“调用”指令中扫描回来,但没有找到足够好的约定,例如,在哪里停止扫描?使用相同参数的两个后续调用会发生什么情况?。非常感谢您的帮助。

转载我的评论作为答复


在优化的代码中,您无法可靠地判断。即使在大多数情况下做好工作也可能需要人工智能。e、 函数在RSI中留下值是因为它是第二个参数,还是只是在计算第一个参数的RDI值时使用RSI作为暂存器?正如Ross所说,gcc为调用约定的堆栈参数生成的代码具有更明显的模式,但仍然不容易检测

由于gcc有时可以并且确实使用mov存储来存储堆栈参数,因此很难区分将局部变量溢出到堆栈的存储与将参数存储到堆栈的存储之间的区别:请参阅。区别的一种方法是,稍后将重新加载局部变量,但arg总是被假定为被击倒

使用相同参数的两个后续调用会发生什么情况

编译器总是在进行另一次调用之前重新写入参数,因为它们假定函数甚至在堆栈上也会对其参数进行阻塞。ABI说函数拥有自己的参数。编译器确实会生成这样的代码以查看注释,但编译器生成的代码并不总是愿意重新使用包含其参数的堆栈内存来存储完全不同的参数,以便启用尾部调用优化:这是hand wavey,因为我不记得我所看到的,因为我错过了尾部调用优化机会

然而,如果参数是通过堆栈传递的,那么它可能是更简单的情况,我的结论是,所有6个寄存器都被使用

即使这样也不可靠。问题并不简单

int fooint,big_struct,int将在regs中传递两个整数参数,但在堆栈上按值传递大结构。FP-args也是一个主要的并发症。您不能得出结论,在堆栈上看到东西意味着使用了所有6个整数arg传递插槽

Windows x64 ABI有着显著的不同:例如,如果在添加隐藏返回值指针(如果需要)后的第二个参数是整数/指针,则它始终位于RDX中,而不管第一个参数位于RCX、XMM0还是堆栈上。它还要求调用者留下阴影空间

因此,您可能能够提出一些启发式方法,以便在未优化的代码中正常工作。即使这样,也很难做到正确


对于由不同编译器生成的优化代码,我认为实现任何接近有用的东西都比使用它节省的工作量要大。

将我的评论作为答案重新发布


在优化的代码中,您无法可靠地判断。即使在大多数情况下做好工作也可能需要人工智能。e、 函数在RSI中留下值是因为它是第二个参数,还是只是在计算第一个参数的RDI值时使用RSI作为暂存器?正如Ross所说,gcc为调用约定的堆栈参数生成的代码具有更明显的模式,但仍然不容易检测

由于gcc有时可以并且确实使用mov存储来存储堆栈参数,因此很难区分将局部变量溢出到堆栈的存储与将参数存储到堆栈的存储之间的区别:请参阅。区别的一种方法是,稍后将重新加载局部变量,但arg总是被假定为被击倒

使用相同参数的两个后续调用会发生什么情况

编译器总是在进行另一次调用之前重新写入参数,因为它们假定函数甚至在堆栈上也会对其参数进行阻塞。ABI说函数拥有自己的参数。编译器确实会生成这样的代码以查看注释,但编译器生成的代码并不总是愿意重新使用包含其参数的堆栈内存来存储完全不同的参数,以便启用尾部调用优化:这是hand wavey,因为我不记得我所看到的,因为我错过了尾部调用优化机会

然而,如果参数是通过堆栈传递的,那么它可能是更简单的情况,我的结论是,所有6个寄存器都被使用

即使这样也不可靠。问题并不简单

int fooint,big_struct,int将在regs中传递两个整数参数,但在堆栈上按值传递大结构。FP-args也是一个主要的并发症。您不能得出结论,在堆栈上看到东西意味着使用了所有6个整数arg传递插槽

Windows x64 ABI的性能显著下降 不同:例如,如果添加隐藏返回值指针(如果需要)后的第二个参数是整数/指针,则它始终位于RDX中,而不管第一个参数位于RCX、XMM0还是堆栈中。它还要求调用者留下阴影空间

因此,您可能能够提出一些启发式方法,以便在未优化的代码中正常工作。即使这样,也很难做到正确


对于由不同编译器生成的优化代码,我认为实现任何接近有用的东西都比使用它节省的工作量大。

GCC有两种不同的策略来调用这样的函数。一种是它将参数推送到堆栈上,然后在函数调用后的某个时间将其清除;另一种是在函数开始时为所有函数调用的传出参数保留空间,并在结束时将其清除一次。无论哪种方式,堆栈上的函数参数在整个调用中都是不稳定的,但只有那些实际传递给该调用的参数才是可变的。这意味着函数的参数可以在调用之前很长时间放在堆栈上,也可以跨其他调用放在堆栈上。即使在大多数情况下做好工作也可能需要人工智能。e、 函数在RSI中留下值是因为它是第二个参数,还是只是在计算第一个参数的RDI值时使用RSI作为暂存器?正如Ross所说,gcc为堆栈参数调用约定生成的代码具有更明显的模式,但仍然不容易检测。@PeterCordes-Hmm。。。我假设使用基于堆栈的调用约定,但是基于寄存器的调用约定会使它完全不可能。在使用相同参数的两个后续调用上会发生什么?编译器总是在进行另一次调用之前重新写入参数,因为它们假定函数甚至在堆栈上也会对其参数进行阻塞。ABI说函数拥有自己的参数。我所见过的编译器生成的代码从来没有真正修改过包含参数的堆栈内存,甚至在启用尾部调用时也没有:这会引发支持或不支持GCC编译优化的冲突。如果不支持优化,那么生成的代码可能会更结构化,但是如果支持优化,我可能不会假设使用另一个reg作为所需reg的草稿行,因为它通常需要额外的指令。GCC有两种不同的策略来调用这样的函数。一种是它将参数推送到堆栈上,然后在函数调用后的某个时间将其清除;另一种是在函数开始时为所有函数调用的传出参数保留空间,并在结束时将其清除一次。无论哪种方式,堆栈上的函数参数在整个调用中都是不稳定的,但只有那些实际传递给该调用的参数才是可变的。这意味着函数的参数可以在调用之前很长时间放在堆栈上,也可以跨其他调用放在堆栈上。即使在大多数情况下做好工作也可能需要人工智能。e、 函数在RSI中留下值是因为它是第二个参数,还是只是在计算第一个参数的RDI值时使用RSI作为暂存器?正如Ross所说,gcc为堆栈参数调用约定生成的代码具有更明显的模式,但仍然不容易检测。@PeterCordes-Hmm。。。我假设使用基于堆栈的调用约定,但是基于寄存器的调用约定会使它完全不可能。在使用相同参数的两个后续调用上会发生什么?编译器总是在进行另一次调用之前重新写入参数,因为它们假定函数甚至在堆栈上也会对其参数进行阻塞。ABI说函数拥有自己的参数。我所见过的编译器生成的代码从来没有真正修改过包含参数的堆栈内存,甚至在启用尾部调用时也没有:这会引发支持或不支持GCC编译优化的冲突。如果不支持优化,那么生成的代码可能会更结构化,但是如果支持优化,我可能不会假设使用另一个reg作为所需reg的草稿行,因为它经常需要额外的指令。实际上,让GCC生成修改用于传递参数的堆栈内存的代码并不难。只需void fooint arg{arg=0;}而不进行优化即可。优化需要编译器将分配给它的寄存器溢出回堆栈。例如:@RossRidge:谢谢。我认为gcc倾向于分配一个本地变量来保存值,即使传入的arg已经死了。也许我从来没有考虑过直接修改C arg,因为这不是我通常编写代码的方式。嗯,即使使用新的C变量也会导致重用传入的arg堆栈插槽。酷,我想我错了:GCC4.7和更老版本使用不同的策略
gy,并且似乎正在针对推送速度较慢的CPU进行优化。我没有看到任何arg插槽的重用。x86和x64之间似乎有所不同,在x64中,GCC 6.3不会重用堆栈参数@Jaaz:当然,在64位模式下不需要,因为有足够的保留调用的寄存器。gcc倾向于保存/恢复函数的寄存器,并使用它在函数调用中保留内容,而不是溢出/重新加载它自己的值。如果被调用的函数恰好完全不接触寄存器,这是延迟的一个胜利。实际上,让GCC生成修改用于传递参数的堆栈内存的代码并不难。只需void fooint arg{arg=0;}而不进行优化即可。优化需要编译器将分配给它的寄存器溢出回堆栈。例如:@RossRidge:谢谢。我认为gcc倾向于分配一个本地变量来保存值,即使传入的arg已经死了。也许我从来没有考虑过直接修改C arg,因为这不是我通常编写代码的方式。嗯,即使使用新的C变量也会导致重用传入的arg堆栈插槽。酷,我想我错了:GCC4.7和更老版本使用了不同的策略,并且似乎在为推送速度较慢的CPU进行优化。我没有看到任何arg插槽的重用。x86和x64之间似乎有所不同,在x64中,GCC 6.3不会重用堆栈参数@Jaaz:当然,在64位模式下不需要,因为有足够的保留调用的寄存器。gcc倾向于保存/恢复函数的寄存器,并使用它在函数调用中保留内容,而不是溢出/重新加载它自己的值。如果被调用的函数恰好根本没有触及寄存器,这是延迟的胜利。