是";“联系”;是否需要从程序集调用c函数?

是";“联系”;是否需要从程序集调用c函数?,c,assembly,linux-kernel,system-calls,calling-convention,C,Assembly,Linux Kernel,System Calls,Calling Convention,我正在编写一个C函数,它将从汇编代码中调用 (具体来说,我想在linux内核中的系统调用处理路径中执行一些检查工作,因此我将在条目_32.S中调度系统调用之前调用c函数) 在定义我的c函数时,我对“asmlinkage”修饰符感到困惑 我知道链接是告诉编译器参数将通过堆栈传递 #定义asmlinkage CPP_asmlinkage_uu属性_uu((regparm(0))) 问题: (1) 当定义这样一个将从汇编代码调用的函数时,是否需要链接 (2) gcc中的默认调用约定是什么。如果我在定

我正在编写一个C函数,它将从汇编代码中调用

(具体来说,我想在linux内核中的系统调用处理路径中执行一些检查工作,因此我将在条目_32.S中调度系统调用之前调用c函数)

在定义我的c函数时,我对“asmlinkage”修饰符感到困惑

我知道链接是告诉编译器参数将通过堆栈传递

#定义asmlinkage CPP_asmlinkage_uu属性_uu((regparm(0)))

问题:

(1) 当定义这样一个将从汇编代码调用的函数时,是否需要链接

(2) gcc中的默认调用约定是什么。如果我在定义c函数时省略了“asmlinkage”,它是否意味着_cdecl或fastcall

(3) 如果默认调用约定是cdecl,那么考虑到cdecl等于asmlinkage修饰符,为什么需要asmlinkage?(我说的对吗?)

(4) 为什么那些系统调用函数都是用asmlinkage声明的。我们可以先将参数复制到寄存器,然后调用那些系统调用函数吗?在我看来,在x86中,当发出系统调用时,参数很容易保存在寄存器中;那么,为什么还要费心在堆栈中保存然后通过堆栈约定强制执行这样的传递参数呢


最后,有谁能推荐一些我可以参考的关于这种混合汇编/c编程的资源/书籍吗

我认为,这个想法是允许内核使用gcc选项进行编译,从而将默认调用约定更改为更高效的方式(即在寄存器中传递更多参数)。但是,根据使用的gcc选项,不能允许需要从asm调用的函数在调用约定上有所不同,或者必须为每个受支持的gcc选项集提供单独的asm版本。因此,需要使用固定调用约定(恰好与默认值匹配,没有特殊的gcc选项)的函数用特殊属性声明,以便它们的调用约定保持固定。

我想自己尝试回答问题(4):

为什么所有系统调用函数sys_u*(例如sys_gettimeofday)都使用堆栈传递参数?

原因是,在处理来自用户空间的系统调用请求时,内核无论如何都需要将所有寄存器保存到堆栈上(以便在返回到用户空间之前恢复环境),这样之后,参数就可以在堆栈上使用了。也就是说,它不需要额外的努力


另一方面,如果您想将fastcall用于调用约定,则需要做更多的工作。我们首先需要知道,当用户程序发出系统调用时,在x86 linux中,%eax表示系统调用号,%ebx,%ecx,%edx,%esi,%edi,%ebp用于向系统调用传递6个参数(在“int 80h”或“syscenter”之前)。但是,fastcall的调用约定是在%eax中传递第一个参数,在%edx中传递第二个参数,在第三个%ecx中传递第二个参数,其他参数从右向左推送到堆栈上。这样,为了在内核中强制执行这样的快速调用约定,除了保存堆栈上的所有寄存器外,还需要以某种方式安排它们

经过几个小时的研究,我得到了以下经验点:

(1)定义将从汇编代码调用的函数时是否需要链接?

没有。实际上,fastcall经常被使用

例如,在条目_32.S中,如果搜索“call”,则可以获得从该程序集文件调用的所有c函数。然后您可以看到,许多人使用fastcall而不是asmlinkage作为调用约定。比如说,

    /*in entry_32.S*/
    movl PT_OLDESP(%esp), %eax
    movl %esp, %edx
    call patch_espfix_desc

    /*in traps_32.c*/
    fastcall unsigned long patch_espfix_desc(unsigned long uesp,
                  unsigned long kesp)
(2)gcc中的默认调用约定是什么。如果我在定义c函数时省略了“asmlinkage”,这是否意味着_cdecl或fastcall?

(3)如果默认调用约定是cdecl,那么考虑到cdecl等于ASMLINK修饰符,为什么需要ASMLINKING?(我说的对吗?

对于未从汇编代码调用的C函数,我们可以安全地假定默认调用约定为cdecl(或快速调用;这无关紧要,因为gcc将负责调用方和被调用方的参数传递。默认调用约定可以在编译时指定)。然而,对于从汇编代码调用的C函数,我们应该显式声明函数的调用约定,因为在汇编端传递代码的参数是固定的。例如,如果patch_espfix_desc被声明为asmlinkage,那么gcc将编译该函数以从堆栈中检索参数。这与汇编端不一致,汇编端将参数放入寄存器


但我仍然不清楚什么时候使用ASM链接,什么时候使用fastcall。我真的需要一些指南和参考资料。

在32位x86上,内核中使用的调用约定似乎并不完全是GNU fastcall(即前两个参数-在
%ecx
%edx
)。它由
-mregparm=3
控制,指示编译器按照该顺序对前3个参数使用
%eax
%edx
%ecx
。这类似于Borland fastcall约定,但仍然是“不需要额外的努力……”但这并不完全正确,是吗?C函数可以将其参数视为局部变量并任意修改。因此,由于寄存器必须跨int 0x80保留,它们实际上必须复制两次:一个副本在返回到用户空间时恢复,另一个副本传递给C
sys.*
函数。实际上,对于x86-32,您可以看到第一个副本已经完成。此块作为指向
struct pt_regs
的指针通过引用传递到C代码中。稍后,通过some,
sys.*
函数被称为
sys.&blah(regs->bx,regs->cx,…)
,这需要第二个副本。