3

I wrote the following code to call syscall exit without linking with glibc:

// a.c
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

_Noreturn void _start()
{
    register int syscall_num asm ("rax") = __NR_exit;
    register int exit_code   asm ("rdi") = 0;

    // The actual syscall to exit
    asm volatile ("syscall"
        : /* no output Operands */
        : "r" (syscall_num), "r" (exit_code));
}

The Makefile:

.PHONY: clean

a.out: a.o
    $(CC) -nostartfiles -nostdlib -Wl,--strip-all a.o

a.o: a.c
    $(CC) -Oz -c a.c

clean:
    rm a.o a.out

I make it with CC=clang-7 and it works perfectly well except that when I inspect the assembly generated by objdump -d a.out:

a.out:     file format elf64-x86-64


Disassembly of section .text:

0000000000201000 <.text>:
  201000:   6a 3c                   pushq  $0x3c
  201002:   58                      pop    %rax
  201003:   31 ff                   xor    %edi,%edi
  201005:   0f 05                   syscall 
  201007:   c3                      retq   

there is a useless retq following the syscall. I wonder, is there any way to remove that without resorting to writing the whole function in assembly?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
JiaHao Xu
  • 2,452
  • 16
  • 31
  • 2
    Fun fact: gcc already does omit the `ret` just because of the `_Noreturn` declaration. https://godbolt.org/z/OLuoUO. But it still warns about a `_Noreturn` function returning, even though it leaves out the `ret`. clang and ICC need `__builtin_unreachable`, though, and also warn. BTW, I simplified the asm statement to use `"a"` and `"D"` constraints instead of register-asm local vars. A `"memory"` clobber for good measure will make sure nothing can be reordered to after the asm statement, even though that's probably already impossible. – Peter Cordes Dec 30 '18 at 08:18
  • @PeterCordes I think it is worth noting that with _Noreturn the `ret` only disappears if using GCC 8.x+ but prior to those releases it didn't. – Michael Petch Dec 30 '18 at 13:08

1 Answers1

6

Add this after the system call that doesn't return:

    __builtin_unreachable();
prl
  • 11,716
  • 2
  • 13
  • 31
  • 1
    FYI, you can set the registers directly: asm( "syscall" :: "a" (SYS_exit), "D" (0) ); – Jeremy Dec 29 '18 at 19:31
  • Fun fact: on ICC `if(x) __builtin_unreachable()` actually compiles into asm that does a conditional jump to past the end of the function. But adding it at the end of this function does just avoid a `ret`, so maybe Intel compiler devs overlooked the inside-a-branch case, because there it's the opposite of helping the optimizer. – Peter Cordes Dec 30 '18 at 08:06