18

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

(具体来说,我想在linux内核中系统调用处理的路径做一些检查工作,所以我会在entry_32.S中调度系统调用之前调用c函数)

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

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

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

问题:

(1) 在定义这样一个将从汇编代码中调用的函数时,是否需要 asmlinkage?

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

(3) 如果默认调用约定是 cdecl,为什么需要 asmlinkage,考虑到 cdecl 等于 asmlinkage 修饰符?(我在这里正确吗?)

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

最后,任何人都可以推荐一些我可以参考的资源/书籍来进行这种混合汇编/c编程吗?

4

3 回答 3

17

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

(1) 在定义这样一个将从汇编代码中调用的函数时,是否需要 asmlinkage?

不,实际上fastcall是经常使用的。

例如在entry_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 中的默认调用约定是什么。如果我在定义 ac 函数时省略了“asmlinkage”,它是否意味着 _cdecl 或 fastcall?

(3) 如果默认调用约定是 cdecl,为什么需要 asmlinkage,考虑到 cdecl 等于 asmlinkage 修饰符?(我在这里正确吗?)

For C functions not invoked from assembly code, we can safely assume the default calling convention is cdecl (or fast call; it doesn't matter, because gcc will take care of the caller and callee for parameter passing. The default calling convention can be specified when compiling). However, for C functions invoked from assembly code, we should explicitly declare the function's calling convention, because the parameter passing code in assembly side has been fixed. For example, if patch_espfix_desc is declared as asmlinkage, then gcc will compile the function to retrieve parameters from stack. This is inconsistent with the assembly side, which put the parameters into registers.

But I am still not clear when to use asmlinkage and when to use fastcall. I really need some guideline and resources to refer to.

于 2012-04-08T14:37:43.673 回答
14

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

为什么所有的系统调用函数sys_*,例如sys_gettimeofday,都使用栈来传递参数?

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

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

于 2012-04-08T05:29:59.630 回答
3

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

于 2012-04-08T04:04:14.603 回答