Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"unsupported for mov" GCC inline assembler

While playing around with GCC's inline assembler feature, I tried to make a function which immediately exited the process, akin to _Exit from the C standard library.

Here is the relevant piece of source code:

void immediate_exit(int code)
{
#if defined(__x86_64__)
    asm (
            //Load exit code into %rdi
            "mov %0, %%rdi\n\t"
            //Load system call number (group_exit)
            "mov $231, %%rax\n\t"
            //Linux syscall, 64-bit version.
            "syscall\n\t"
            //No output operands, single unrestricted input register, no clobbered registers because we're about to exit.
            :: "" (code) :
    );
//Skip other architectures here, I'll fix these later.
#else
#   error "Architecture not supported."
#endif
}

This works fine for debug builds (with -O0), but as soon as I turn optimisation on at any level, I get the following error:

immediate_exit.c: Assembler messages:
immediate_exit.c:4: Error: unsupported for `mov'

So I looked at the assembler output for both builds (I've removed .cfi* directives and other things for clarity, I can add that in again if it's a problem). The debug build:

immediate_exit:
.LFB0:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)

    mov -4(%rbp), %rdi
    mov $231, %rax
    syscall

    popq    %rbp
    ret

And the optimised version:

immediate_exit:
.LFB0:
    mov %edi, %rdi
    mov $231, %rax
    syscall

    ret

So the optimised version is trying to put a 32-bit register edi into a 64-bit register, rdi, rather than loading it from rbp, which I presume is what is causing the error.

Now, I can fix this by specifying 'm' as a register constraint for code, which causes GCC to load from rbp regardless of optimisation level. However, I'd rather not do that, because I think the compiler and its authors has a much better idea about where to put stuff than I do.

So (finally!) my question is: how do I persuade GCC to use rdi rather than edi for the assembly output?

like image 862
Michael Rawson Avatar asked Dec 16 '22 06:12

Michael Rawson


2 Answers

Overall, you're much better off using constraints to get values into the right registers rather than explicit moves:

#include <asm/unistd.h>

    asm volatile("syscall" 
      :  // no outputs.  Other syscalls need an "=a"(retval) to tell the compiler RAX is modified, whether you actually use the retval or not.
      : "D" ((uint64_t)code), "a" ((uint64_t)__NR_exit_group)  // 231
      : "rcx", "r11"    // syscall itself clobbers these.  exit can't fail and return; mostly here as an example for other syscalls
        , "memory"      // make sure any stores, e.g. to mmapped files, are done before this
       );
    __builtin_unreachable();   // tell the compiler execution doesn't come out the bottom of the asm statement.  Maybe have the same effect as a "memory" clobber of making sure not to delay stores which could potentially be to mmapped files or shared memory. 

That lets compiler hoist the moves earlier in the code if useful, or even avoid the move altogether if the value can be arranged to already be in the correct register...

For example code will be in EDI if this function doesn't inline; the Linux system-calling convention was chosen to be as close as possible to the x86-64 System V function-calling convention, except for using R10 instead of RCX because the syscall instruction itself overwrites it with saved-RIP, and R11 with saved-RFLAGS.

(Unnecessarily casting (uint64_t)code would force the compiler to redo zero-extension with a mov %edi, %edi in that case, though. The call number does need to be zero-extended to 64-bit, which will almost certainly happen for free even if you didn't manually cast it (since the compiler will use a mov $231, %eax), but it doesn't hurt to be explicit about something that is required. The exit_group system call takes a 32-bit int arg, so the kernel is guaranteed to ignore high garbage in RDI.)

like image 133
Chris Dodd Avatar answered Jan 12 '23 17:01

Chris Dodd


Cast your variable into the appropriate length type.

#include <stdint.h>

asm (
            //Load exit code into %rdi
            "mov %0, %%rdi\n\t"
            //Load system call number (group_exit)
            "mov $231, %%rax\n\t"
            //Linux syscall, 64-bit version.
            "syscall\n\t"
            //No output operands, single unrestricted input register, no clobbered registers because we're about to exit.
            :: "g" ((uint64_t)code)
    );

or better have your operand type straight away of the right size:

void immediate_exit(uint64_t code) { ...
like image 44
Sergey L. Avatar answered Jan 12 '23 16:01

Sergey L.