代码之家  ›  专栏  ›  技术社区  ›  IS4

跳转/尾随调用另一个函数

  •  1
  • IS4  · 技术社区  · 6 年前

    void f1(...);
    void f2(...);
    

    我可以改变你的身体 f1 ,但是 f2 在另一个库中定义,我无法更改。我 绝对地 f2层 f1层 f1层 f2层 但是,据我所知,这是不可能的纯C或C++。我们别无选择 接受一个 va_list 很不幸。呼叫 f2层 在函数中最后发生,所以我需要某种形式的尾调用。

    f2层

    __asm {
        mov eax, f2
        leave
        jmp eax
    }
    

    这个汇编代码是不是不正确,或者编译器的一些优化是否破坏了这个代码?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Peter Cordes    6 年前

    你必须写作 f1

    const ).

    void foo(int x) { x += 1; bar(x); } 可能会修改保存的返回地址上方的堆栈空间 x ,如果在禁用优化的情况下编译。使用相同的参数进行另一个呼叫需要再次存储它们,除非您知道被叫方没有踩到它们。同样的参数也适用于从一个函数末尾进行的tailcalling。

    on the Godbolt compiler explorer add DWORD PTR [ebp+8], 1 推之前 [ebp+8] .


    定义 在你的职能中,如果你能说服他们追查,你就可以逃脱惩罚。

    void bar(...); 不过,C语言中的一个有效原型:

    # gcc -xc on Godbolt to force compiling as C, not C++
    <source>:1:10: error: ISO C requires a named argument before '...'
    

    but not in C mode . (GOOBET有一个独立的C模式,它有一组不同的编译器,可以用来获得MSVC来编译代码,而不是C++。我不知道有什么命令行选项可以像gcc那样将其转换为C模式 -xc -xc++


    不管怎样, 它可能工作(在优化的构建中)来编写 f2(); f1层 ,但对于传递的参数,这对编译器来说是肮脏的和完全的谎言。当然,只适用于没有寄存器参数的调用约定(但您显示的是32位asm,因此很可能使用的是没有寄存器参数的调用约定。)

    jmp f2 在这种情况下进行优化的尾部调用,因为它们都返回 void . (对于非空,你会 return f2();


    顺便说一句,如果 mov eax, f2 那就行了 jmp f2型 也可以。

    即使在调试版本中也是不安全的,因为编译器可能 push 删除了一些保留调用的寄存器,这些寄存器需要在离开函数之前(以及在运行之前)弹出 leave 销毁堆栈帧)。


    @mevets展示的蹦床思想可以简化:如果args上有一个合理的固定大小上限,那么您可以将64或128字节的潜在args从传入args复制到args中以供使用 f1层 . 几个SIMD矢量就可以了。然后你可以打电话 正常情况下,尾号呼叫 f2 从你的asm包装。

    如果存在可能的register参数,请在复制参数之前将它们保存到堆栈空间,并在调用之前还原它们。

        2
  •  2
  •   mevets    6 年前

    编译器不会在您正在挖掘的点上做出任何保证。一个蹦床功能可能会起作用,但你必须保存它们之间的状态,并做大量的挖掘。

    * argn, ..., arg0, retaddr */
    trampoline:
        push < all volatile regs >
        call <get thread local storage >
        copy < volatile regs and ret addr > to < local storage >
        pop < volatile regs >
        remove ret addr
        call  f2
        call < get thread local storage >
        restore < volatile regs and ret addr>
        jmp f1
        ret
    
    推荐文章