【问题标题】:Is "asmlinkage" required for a c function to be called from assembly?从程序集中调用 c 函数是否需要“asmlinkage”?
【发布时间】:2012-04-21 01:21:05
【问题描述】:

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

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

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

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

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

问题:

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

(2) gcc 中的默认调用约定是什么。如果我在定义一个c函数时省略了“asmlinkage”,它是暗示_cdecl还是fastcall?

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

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

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

【问题讨论】:

    标签: c assembly linux-kernel system-calls calling-convention


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

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

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

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

      【讨论】:

      • “它不需要额外的努力......”但这并不完全正确,是吗? C 函数允许将其参数视为局部变量并任意修改它们。因此,由于寄存器必须跨 int 0x80 保留,它们实际上必须被复制两次:一个副本在返回用户空间时恢复,另一个副本传递给 C sys_* 函数。
      • 确实,对于 x86-32,您可以看到第一个副本正在完成 here。该块作为指向struct pt_regs 的指针通过引用传递到C 代码中。稍后,通过一些tricky macrossys_* 函数被称为sys_blah(regs->bx, regs->cx, ...),这需要第二个副本。
      【解决方案3】:

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

      (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 中的默认调用约定是什么。如果我在定义 c 函数时省略了“asmlinkage”,它是指 _cdecl 还是 fastcall?

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

      对于不是从汇编代码中调用的 C 函数,我们可以放心地假设默认调用约定是 cdecl(或快速调用;没关系,因为 gcc 将负责调用者和被调用者的参数传递。默认调用编译时可以指定约定)。但是,对于从汇编代码调用的 C 函数,我们应该显式声明函数的调用约定,因为汇编端的参数传递代码已经被修复。例如,如果 patch_espfix_desc 声明为 asmlinkage,则 gcc 将编译该函数以从堆栈中检索参数。这与汇编端的做法不一致,汇编端将参数放入寄存器中。

      但是我还是不清楚什么时候用asmlinkage,什么时候用fastcall。我真的需要一些指南和资源来参考。

      【讨论】:

      • 在 32 位 x86 上,内核中使用的调用约定似乎不完全是 GNU fastcall(即前 2 个参数 - 在 %ecx%edx 中)。它由-mregparm=3GCC option 管理,指示编译器按顺序使用%eax%edx%ecx 作为前3 个参数。这类似于 Borland fastcall 约定,但仍然如此。
      猜你喜欢
      • 2021-08-13
      • 2018-06-04
      • 2010-10-29
      • 2011-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-30
      • 1970-01-01
      相关资源
      最近更新 更多