【问题标题】:Is it possible to count the function (variable) arguments in a __cdecl with Inline ASM?是否可以使用内联 ASM 计算 __cdecl 中的函数(变量)参数?
【发布时间】:2019-07-31 01:25:01
【问题描述】:

被调用者是否可以通过使用内联 ASM (x86) 在不知道参数的类型或数量的情况下偏移堆栈基指针 (rbp) 来迭代(和计数)函数调用参数?

void foo(char *arg, ...);

我使用的是 Intel 编译器,但它的文档声明它支持 GCC 样式的内联汇编。所以基于 GCC 的例子就足够了。

#include <stdio.h>
#include <inttypes.h>

int    main(int argc, char **argv){

    uint64_t n;

    __asm__ __volatile__(
      "movq %%rbp, %0\n\t"
      : "=r"(n)
    );

    printf("rbp = 0x%" PRIx64 "\n", n);
    return 0;
}

the code in this post

【问题讨论】:

  • 不,被调用者无法确定参数的数量。
  • 你可以使用 ... 传递给一个函数并有任意数量的参数,如果这会导致任何帮助
  • 在几乎所有的 C 实现中,没有。 C 的原始设计依赖于调用者和被调用者之间的契约,由程序员决定和执行。例如 printf 的约定是 % 格式说明符的数量将匹配参数的数量。
  • 有时甚至无法恢复参数,除非您知道它们的类型

标签: c assembly x86-64 variadic-functions calling-convention


【解决方案1】:

唯一可行的方法是使用标记最后一个参数的唯一标记值(例如 NULL 指针)。

这通常仅在所有参数都是指针时才有效,例如由POSIX execl(3) functions 使用,带有类似

的签名
int execl(const char *pathname, const char *arg, ...
                   /* (char  *) NULL */);

(那么你不需要内联 asm;你可以使用 C VA_ARG 宏。)


另外,rbp 没用;您不知道该函数是否在启用优化的情况下编译,因此它可能根本不是帧指针。如果您想要 GNU C 中的帧地址,请使用 __builtin_frame_address(0)。 (查看编译器生成的 asm 以了解它的作用。IIRC,它强制 -fno-omit-frame-pointer 为该函数提供 rbp 的值)

即使获取堆栈帧地址也无法帮助您获取寄存器参数(x86-64 System V 中的前 6 个整数/指针和前 8 个 FP 参数。或者 Windows x64 中的前 4 个总参数。)

将您的函数声明为可变参数并使用VA_ARG 将所有可变参数参数读取为uint64_t,这样您就可以检查它们是否为零,或者您选择作为哨兵的任何位模式。

显然这需要调用者的配合才能通过哨兵。


顺便说一句,x86-64 不存在名为 __cdecl 的调用约定。微软称他们的 x64 为 __fastcall__vectorcall

【讨论】:

    【解决方案2】:

    不,你不能。一般情况下是不可能的。

    在使用调试符号编译的特定情况下,您可以编写代码来解释您自己的符号,但这种编译模式对于发布版本不赞成,因此不建议编写此代码。它也是完全不可移植的,但你不关心这个限制。

    【讨论】:

    • 是的,C 没有反射,所以从调试信息中获取反射将是一个彻头彻尾的黑客攻击。
    猜你喜欢
    • 2011-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-18
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多