函数调用中传递了多少个参数?
How many arguments are passed in a function call?
我想分析调用函数的汇编代码,并针对每个 'call' 找出传递给函数的参数数量。我假设我无法访问目标函数,只能访问调用代码。
我将自己限制在仅使用 GCC 编译的代码以及 System V ABI 调用约定。
我尝试从每个 'call' 指令中扫描回来,但我没有找到足够好的约定(例如,在哪里停止扫描?使用相同参数的两个后续调用会发生什么?)。非常感谢您的帮助。
重新发布我的评论作为答案。
您无法在优化代码中可靠地判断。即使大部分时间做得很好,也可能需要人类水平的人工智能。例如一个函数是否因为它是第二个参数而在 RSI 中留下了一个值,或者它只是在计算 RDI(第一个参数)的值时将 RSI 用作临时寄存器?正如 Ross 所说,gcc 为 stack-args 调用约定生成的代码具有更明显的模式,但仍然不容易检测到。
也可能很难区分将局部变量溢出到堆栈的存储与将参数存储到堆栈的存储之间的区别(因为 gcc 有时可以并且确实使用 mov
存储来存储堆栈参数:请参阅-maccumulate-outgoing-args
)。区分的一种方法是稍后将重新加载局部变量,但始终假定 args 已被破坏。
what happen on two subsequent calls with the same arguments?
编译器总是在进行另一个调用之前重写 args,因为他们假设函数破坏了他们的 args(甚至在堆栈上)。 ABI 表示函数 "own" 他们的参数。编译器确实会生成执行此操作的代码(请参阅评论),但编译器生成的代码并不总是愿意重新调整保存其参数的堆栈内存的用途,以存储完全不同的参数以启用尾调用优化。 :( 这是手波式的,因为我不记得我到底看到了什么,因为错过了尾调用优化机会。
Yet if arguments are passed by the stack, then it shall probably be the easier case (and I conclude that all 6 registers are used as well).
即使那样也不可靠。 System V x86-64 ABI不简单。
int foo(int, big_struct, int)
会在 regs 中传递两个整数参数,但会在堆栈上按值传递大结构。 FP args 也是一个主要的并发症。你不能得出结论,看到堆栈上的东西意味着所有 6 个整数 arg 传递槽都被使用了。
Windows x64 ABI 明显不同:例如,如果第二个参数(如果需要,在添加隐藏的 return 值指针之后)是 integer/pointer,它总是在 RDX 中,无论第一个 arg 是进入 RCX、XMM0 还是堆栈。它还要求呼叫者离开 "shadow space".
因此,您或许可以想出一些启发式方法,以便对未优化的代码正常工作。即使那样也很难做到正确。
对于由不同编译器生成的优化代码,我认为实现任何接近有用的东西都比拥有它所节省的工作要多。
我想分析调用函数的汇编代码,并针对每个 'call' 找出传递给函数的参数数量。我假设我无法访问目标函数,只能访问调用代码。 我将自己限制在仅使用 GCC 编译的代码以及 System V ABI 调用约定。 我尝试从每个 'call' 指令中扫描回来,但我没有找到足够好的约定(例如,在哪里停止扫描?使用相同参数的两个后续调用会发生什么?)。非常感谢您的帮助。
重新发布我的评论作为答案。
您无法在优化代码中可靠地判断。即使大部分时间做得很好,也可能需要人类水平的人工智能。例如一个函数是否因为它是第二个参数而在 RSI 中留下了一个值,或者它只是在计算 RDI(第一个参数)的值时将 RSI 用作临时寄存器?正如 Ross 所说,gcc 为 stack-args 调用约定生成的代码具有更明显的模式,但仍然不容易检测到。
也可能很难区分将局部变量溢出到堆栈的存储与将参数存储到堆栈的存储之间的区别(因为 gcc 有时可以并且确实使用 mov
存储来存储堆栈参数:请参阅-maccumulate-outgoing-args
)。区分的一种方法是稍后将重新加载局部变量,但始终假定 args 已被破坏。
what happen on two subsequent calls with the same arguments?
编译器总是在进行另一个调用之前重写 args,因为他们假设函数破坏了他们的 args(甚至在堆栈上)。 ABI 表示函数 "own" 他们的参数。编译器确实会生成执行此操作的代码(请参阅评论),但编译器生成的代码并不总是愿意重新调整保存其参数的堆栈内存的用途,以存储完全不同的参数以启用尾调用优化。 :( 这是手波式的,因为我不记得我到底看到了什么,因为错过了尾调用优化机会。
Yet if arguments are passed by the stack, then it shall probably be the easier case (and I conclude that all 6 registers are used as well).
即使那样也不可靠。 System V x86-64 ABI不简单。
int foo(int, big_struct, int)
会在 regs 中传递两个整数参数,但会在堆栈上按值传递大结构。 FP args 也是一个主要的并发症。你不能得出结论,看到堆栈上的东西意味着所有 6 个整数 arg 传递槽都被使用了。
Windows x64 ABI 明显不同:例如,如果第二个参数(如果需要,在添加隐藏的 return 值指针之后)是 integer/pointer,它总是在 RDX 中,无论第一个 arg 是进入 RCX、XMM0 还是堆栈。它还要求呼叫者离开 "shadow space".
因此,您或许可以想出一些启发式方法,以便对未优化的代码正常工作。即使那样也很难做到正确。
对于由不同编译器生成的优化代码,我认为实现任何接近有用的东西都比拥有它所节省的工作要多。