4
votes

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?

2

2 Answers

3
votes

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) { ...
9
votes

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

asm("syscall" :: "D" ((uint64_t)code), "a" ((uint64_t)231));

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...