← Back to context

Comment by jmillikin

12 days ago

As other comments have noted, the asm statement needs to have its input/output registers specified to ensure the compiler doesn't erase the "unused" values.

Working example: https://john-millikin.com/unix-syscalls#linux-x86-64-gnu-c

Adapted to use main():

  static const int STDOUT = 1;
  static const int SYSCALL_WRITE = 1;
  static const char message[] = "Hello, world!\n";
  static const int message_len = sizeof(message);

  int main() {
   register int         rax __asm__ ("rax") = SYSCALL_WRITE;
   register int         rdi __asm__ ("rdi") = STDOUT;
   register const char *rsi __asm__ ("rsi") = message;
   register int         rdx __asm__ ("rdx") = message_len;
   __asm__ __volatile__ ("syscall"
    : "+r" (rax)
    : "r" (rax), "r" (rdi), "r" (rsi), "r" (rdx)
    : "rcx", "r11");
   return 0;
  }

Test with:

  $ gcc -o hello hello.c
  $ ./hello
  Hello, world!

Or just

  int main(void) {
    asm volatile("syscall" : : "a"(1), "d"(14), "D"(1), "S"("hello world!\n"));
    return 0;
  }

Though the clobber list is weak spot, I don't know exactly what it should have in this case.

  • You want:

        long ax;
        asm volatile("syscall" : "=a"(ax) : "0"(1), "D"(1), "S"("hello world!\n"), "d"(14));
    

    You can also say:

        long ax = 1;
        asm volatile("syscall" : "+a"(ax) : "D"(1), "S"("hello world!\n"), "d"(14));
    

    https://justine.lol/dox/rmsiface.txt

    • Got some sleep and took a second look. You actually want:

          long ax = 1;
          asm volatile("syscall" : "+a"(ax) : "D"(1), "S"("hello world!\n"), "d"(14) : "rcx", "r11");
      

      Sorry folks! Note also this only works on Linux. On BSDs for example, even if you change the magic number, BSDs may clobber all the call-clobbered registers. So with those OSes it's usually simplest to write an assembly stub like this:

          my_write:
            mov $4,%eax
            syscall
            ret

      2 replies →