Search code examples
linuxgcclinux-kernelx86function-attributes

Does asmlinkage mean stack or registers?


In most languages, C included, the stack is used for function calls. That's why you get a "Stack Overflow" error if you are not careful in recursion. (Pun not intended).

If that is true, then what is so special about the asmlinkage GCC directive.

It says, from #kernelnewbies

The asmlinkage tag is one other thing that we should observe about this simple function. This is a #define for some gcc magic that tells the compiler that the function should not expect to find any of its arguments in registers (a common optimization), but only on the CPU's stack.

I mean I don't think the registers are used in normal function calls.

What is even more strange is when you learn it is implemented using the GCC regparm function attribute on x86.

The documentation of regparm is as follows:

On x86-32 targets, the regparm attribute causes the compiler to pass arguments number one to number if they are of integral type in registers EAX, EDX, and ECX instead of on the stack.

This is basically saying the opposite of what asmlinkage is trying do.

So what happens? Are they on the stack or in the registers.

Where am I going wrong?

The information isn't very clear.


Solution

  • On x86 32bit, the asmlinkage macro expands to __attribute__((regparam(0))), which basically tells GCC that no parameters should be passed through registers (the 0 is the important part). As of Linux 5.17, x86-32 and Itanium64 seem to be the only two architectures re-defining this macro, which by default expands to no attribute at all.

    So asmlinkage does not by itself mean "parameters are passed on the stack". By default, the normal calling convention is used. This includes x86 64bit, which follows the System V AMD64 ABI calling convention, passing function parameters through RDI, RSI, RDX, RCX, R8, R9, [XYZ]MM0–7.

    HOWEVER there is an important clarification to make: even with no special __attribute__ to force the compiler to use the stack for parameters, syscalls in recent kernel versions still take parameters from the stack indirectly through a pointer to a pt_regs structure (holding all the user-space registers saved on the stack on syscall entry). This is achieved through a moderately complex set of macros (SYSCALL_DEFINEx) that does everything transparently.

    So technically, although asmlinkage does not change the calling convention, parameters are not passed inside registers as one would think by simply looking at the syscall function signature.

    For example, the following syscall:

    SYSCALL_DEFINE3(something, long, one, long, two, long, three)
    {
        // ...
        do_something(one, two, three);
        // ...
    }
    

    Actually becomes (roughly):

    asmlinkage __x64_sys_something(struct pt_regs *regs)
    {
        // ...
        do_something(regs->di, regs->si, regs->dx);
        // ...
    }
    

    Which compiles to something like:

    /* ... */
    mov    rdx,QWORD PTR [rdi+0x60]
    mov    rsi,QWORD PTR [rdi+0x68]
    mov    rdi,QWORD PTR [rdi+0x70]
    call   do_something
    /* ... */