调试:我不';在反汇编调用程序中,我看不到回调是callq还是内联asm(Linux x86_64)

调试:我不';在反汇编调用程序中,我看不到回调是callq还是内联asm(Linux x86_64),linux,debugging,assembly,gcc,x86-64,Linux,Debugging,Assembly,Gcc,X86 64,我在CentOS 7.9上使用崩溃实用程序调试内核oops(vmcore),我有一个调用回调的函数foo,但当我反汇编foo时,我看不到引用回调的callq指令,也看不到调用方中回调的程序集(表明它没有内联) 然而,内核堆栈显示RIP位于回调函数的偏移量33上。有什么好处 foo的最后一条指令显示: callq 0xffffffff91c9af10 <__stack_chk_fail> 更新 我也看到了一个callq: \uuuuux86\u间接\uThunk\uRax 但我不知

我在CentOS 7.9上使用崩溃实用程序调试内核oops(vmcore),我有一个调用回调的函数foo,但当我反汇编foo时,我看不到引用回调的callq指令,也看不到调用方中回调的程序集(表明它没有内联)

然而,内核堆栈显示RIP位于回调函数的偏移量33上。有什么好处

foo的最后一条指令显示:

callq  0xffffffff91c9af10 <__stack_chk_fail>
更新 我也看到了一个callq:
\uuuuux86\u间接\uThunk\uRax

但我不知道。。也许这就是电话?仔细看,它与一个回程蹦床有关,听起来很有趣!XD

通常您应该看到
调用*%rcx
或其他内容(函数指针可能位于不同的寄存器或堆栈内存中,但可能是某种间接调用)。或者使用优化的tailcall,如
jmp*%rcx
,如果您的代码可以优化为
return func(args)
。同样,可能是在将函数指针移动到另一个寄存器之后,以及在清理堆栈之后

(第四个整数/指针函数arg到达RCX,但最终使用时它可能位于任何其他寄存器中)

call\u stack\u chk\u fail
只是
-fstack protector=strong
机器的一部分


内核代码使用
gcc-mindirect branch=thunk
为间接调用启用gcc的幽灵缓解,因此是的,间接调用将通过
\uuux86\u indirect\u thunk\u rax
。(或在任何其他寄存器中使用指针的另一个寄存器。)

当然,用户空间代码也可以使用这个选项进行编译,尽管我认为大多数发行版在默认情况下都不会启用它

typedef int (*my_callback_t)(int);

int foo(int a, int b, int c, my_callback_t func)
{
    int ret = func(a);
    return ret;
}
像这样编译,使用
-O3-Wall-mindirect branch=thunk
-mcmodel=kernel
)也可以进行良好的度量)

这是一个很好的例子。


如果没有
-mindirect branch=thunk
,您当然会得到预期的
jmp*%rcx

我是否会看到%rdi在callq之前填充回调的第一个参数(由反汇编的调用方)?或者,如果函数参数在调用者中是本地的,那么寄存器是否不需要遵循参数传递的约定?我看到的唯一无法解释的另一个调用是对uuux86_indirect_thunk_rax的调用,我猜您引用的寄存器优化与此相关?@GreggLeventhal:哦,对了,我忘了内核代码将在启用幽灵缓解的情况下编译。更新。谢谢,这是一个很好的答案。如果你对在纽约地区从事Linux系统(编程或管理)的工作感兴趣,请留言给我!
typedef int (*my_callback_t)(int);

int foo(int a, int b, int c, my_callback_t func)
{
    int ret = func(a);
    return ret;
}
# your function
foo:
        jmp     __x86_indirect_thunk_rcx  # like jmp *%rcx  tailcall 

# extra code that will be present once in the whole kernel, deduplicated by the linker
.section        .text.__x86_indirect_thunk_rcx,"axG",@progbits,__x86_indirect_thunk_rcx,comdat
__x86_indirect_thunk_rcx:
        call    .LIND1
.LIND0:
        pause
        lfence            # block speculation along this never-executed return path that return prediction will jump to.
        jmp     .LIND0    # this seems unnecessary after lfence in this unreachable code.
.LIND1:
        mov     %rcx, (%rsp)       # overwrite the return address with your func ptr
        ret                        # and pop it into RIP