【问题标题】:How many arguments are passed in a function call?函数调用中传递了多少个参数?
【发布时间】:2016-12-18 20:48:44
【问题描述】:

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

【问题讨论】:

  • GCC 有两种不同的策略来调用这样的函数。一是它把参数压入堆栈,然后在函数调用后的某个时间清理它们,二是在函数开始时为所有函数调用的传出参数保留空间,并在结束时清理一次.无论哪种方式,堆栈上的函数参数在调用中都是可变的,但只有那些实际传递给该派系的参数。这意味着函数的参数可以在调用之前很久以及跨其他调用放入堆栈。
  • 您无法在优化的代码中可靠地分辨出来。而且即使大部分时间做得好,也可能需要人类级别的人工智能。例如函数是否在 RSI 中留下了一个值,因为它是第二个参数,或者它只是在计算 RDI 的值(第一个参数)时将 RSI 用作临时寄存器?正如罗斯所说,gcc 为堆栈参数调用约定生成的代码具有更明显的模式,但仍然不容易检测到。
  • @PeterCordes 嗯...我假设一个基于堆栈的调用约定,但是是的,基于寄存器的调用约定将使它完全不可能。
  • 使用相同参数的两个后续调用会发生什么? 编译器总是在进行另一个调用之前重写 args,因为它们假设函数会破坏它们的 args(即使在堆栈上也是如此) )。 ABI 说函数“拥有”它们的参数。我见过的编译器生成的代码从未真正修改过保存其参数的堆栈内存,即使这会启用尾调用:(
  • 这会引发支持或不支持 GCC 编译优化的冲突。如果不支持优化,那么生成的代码可能会更有条理,但如果支持优化,我可能不会假设另一个 reg 用作所需 reg 的暂存器的情况,因为它通常需要额外的指令。

标签: function assembly call reverse-engineering


【解决方案1】:

转发我的 cmets 作为答案。

您无法在优化的代码中可靠地分辨出来。而且即使大部分时间做得好,也可能需要人类级别的人工智能。例如函数是否在 RSI 中留下了一个值,因为它是第二个参数,或者它只是在计算 RDI 的值(第一个参数)时将 RSI 用作临时寄存器?正如 Ross 所说,gcc 生成的堆栈参数调用约定代码具有更明显的模式,但仍然不容易检测到。

还可能很难区分将本地变量溢出到堆栈的存储与将 args 存储到堆栈的存储之间的区别(因为 gcc 有时可以并且确实使用 mov 存储堆栈参数:请参阅 -maccumulate-outgoing-args) .区分的一种方法是稍后将重新加载本地变量,但始终假定 args 已被破坏。

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

编译器总是在进行另一个调用之前重写 args,因为他们假设函数会破坏它们的 args(甚至在堆栈上)。 ABI 说函数“拥有”它们的参数。编译器确实会生成执行此操作的代码(请参阅 cmets),但编译器生成的代码并不总是愿意重新使用保存其 args 的堆栈内存来存储完全不同的 args 以启用尾调用优化。 :( 这是手摇,因为我不记得我所看到的错过尾调用优化机会的确切内容。


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

即使这样也不可靠。 System V x86-64 ABI 并不简单。

int foo(int, big_struct, int) 将在 regs 中传递两个整数参数,但在堆栈上按值传递大结构。 FP args 也是一个主要的并发症。你不能断定看到堆栈上的东西意味着所有 6 个整数 arg 传递槽都被使用了。

Windows x64 ABI 明显不同:例如,如果第二个参数(如果需要,在添加隐藏的返回值指针之后)是整数/指针,它总是进入 RDX,不管第一个参数是否进入 RCX ,XMM0,或在堆栈上。它还要求调用者离开“影子空间”。


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

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

【讨论】:

  • 实际上让 GCC 生成修改用于传递参数的堆栈内存的代码并不难。只需void foo(int arg) { arg = 0;} 无需优化即可。通过优化,它要求编译器必须将为其分配的寄存器溢出回堆栈。例如:godbolt.org/g/ogRl6n
  • @RossRidge:谢谢。我认为 gcc 倾向于分配一个本地来保存值,即使传入的 arg 是“死的”。也许我从未考虑过直接修改 C arg,因为这不是我通常编写代码的方式。嗯,即使使用新的 C 变量也会导致重用传入的 arg 堆栈槽。 godbolt.org/g/8rRoxx。酷,我想我错了 :) gcc 4.7 及更早版本使用不同的策略,并且似乎正在针对 push 速度较慢的 CPU 进行优化。我在那里看不到任何 arg 插槽的重用。 godbolt.org/g/nmRGnV
  • 在 x86 和 x64 之间似乎有所不同,在 x64 中,GCC 6.3 不会重用堆栈参数。 godbolt.org/g/Xo15f8
  • @Jaaz:当然,在 64 位模式下不需要,因为有足够的调用保留寄存器。 gcc 倾向于保存/恢复函数的寄存器并使用它来保存函数调用之间的内容,而不是溢出/重新加载它自己的值。如果被调用的函数碰巧根本不接触寄存器,这是一个延迟的胜利。
猜你喜欢
  • 1970-01-01
  • 2019-03-21
  • 2019-11-17
  • 2021-02-11
  • 1970-01-01
  • 2016-08-19
  • 1970-01-01
  • 2013-08-19
  • 2019-05-07
相关资源
最近更新 更多