使用 `rax` 到 `jmp` 到一个函数是否安全?

Is it safe to use `rax` to `jmp` to a function?

您好,我遇到过需要将参数传递给如下预定义函数的情况:

void dojmp(args...)
{
    args[0]->func(args...);
}

与其写多个跳转函数来传递不同的参数,不如使用裸跳转,这样跳转之后,相对的栈和寄存器数据都没有改变。像下面这样。

void __attribute__((naked)) dojmp(void)
{
    __asm__ __volatile__    (   
        "jmp %%rax  \n\t"
        :
        :"a"(args[0]->func)
        :"memory"                   
    );
}

而gcc编译结果为:

<dojmp>:
  mov    %rdi,%rax
  mov    (%rax),%rax
  jmpq   *%rax

现在我的问题是,使用 rax 作为跳转目标(对于 gcc)和 void return 函数是否安全?

因为通常 rax 会存储 returning 值。但是我调用的所有函数都是无效的....


在这里记一下,把相对地址传给汇编代码,以防我记错了。

#include <stdio.h>

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)


struct have_func{
    int a;
    void* func;
};

int main()
{
    printf("hello world\n");
}

void __attribute__((naked)) dojmp(struct have_func * args)
{
    asm volatile(
        "jmpq *%P0(%%rdi)\n\t" 
        :
        : "i"(offsetof(struct have_func, func))
    );
}

为此您不需要内联 asm。 调用此包装函数应该完全等同于将函数指针强制转换为您用于包装的同一原型。(就传递给目标函数的参数而言。)


请记住,RAX return 值寄存器直到 后才由函数写入,所以当然可以使用 RAX 作为临时 before 尾调用,甚至是具有 return 值的函数。那不是问题。你弄乱了 RAX,然后函数运行,然后它 returns 给你的调用者。

但是,x86-64 System V 确实将 AL 用于可变参数函数以传递 XMM 寄存器 arg 的计数。如果您的函数实际上是可变的,在实际的 C 源代码中使用 void foo(arg a, ...) 和文字 ... ,那么踩到它是有问题的。然后需要传递 AL(为了现代实现的效率,以及在一般情况下的正确性,包括将使用 AL 计算跳转到一系列 movdqa 存储的旧 GCC 代码生成。)

naked 函数中使用 Extended Asm 无论如何在技术上都是不安全的或不受支持的(因为处理操作数涉及编译器生成的代码)。因此,出于多种原因,您最好的选择是使用内存间接跳转,而不是让编译器为您加载:

 asm( "jmpq *(%rdi)" );

offsetof(args, func) 硬编码为 0,因此您可以 static_assert 对其(或将其放置在结构中的任何位置)进行

使用 "i"(offsetof(..))"m"(args[0]->func) 之类的扩展 Asm 可能有效,但 a naked function 在技术上不受支持。 (因为它是扩展 asm)。

This attribute allows the compiler to construct the requisite function declaration, while allowing the body of the function to be assembly code. The specified function will not have prologue/epilogue sequences generated by the compiler. Only basic asm statements can safely be included in naked functions (see Basic Asm). While using extended asm or a mixture of basic asm and C code may appear to work, they cannot be depended upon to work reliably and are not supported.


在编译器生成的 asm 中引入这种额外的间接(并击败内联)只是为了让编译器对函数类型感到满意有点糟糕。 https://gcc.gnu.org/wiki/DontUseInlineAsm

如果你能完全避免内联 asm 就更好了,尽管这可能会以使用可变函数指针类型为代价,但实际上将它指向非可变函数。 x86-64 System V 可以很好地处理这个问题,至少如果 none args 在你不需要它们时最终默认提升。

args[0]->func(args, ...)

例如

  void (*arg0func)(struct Arg, ...) = ( void (*)(struct Arg, ...) )args[0]->func;
  arg0func(args, whatever you were going to pass);

您可以用 CPP 宏包装它。

甚至可能有一种编写可变参数函数的方法,可以将其所有参数内联并转发到另一个函数。