Learn more about Russian war crimes in Ukraine.

How to make a system call in C

Here’s how we might write “hello world” in C:

#include <stdio.h>
int main(void) {
  printf("hello, world!\n");
  return 0;
}

The above program uses printf, which under the hood makes a system call to write those bytes to stdout. We can see this using strace:

$ cc hello.c
$ strace ./a.out
...
write(1, "hello, world!\n", 14)         = 14
...

strace conveniently shows us these system calls using C syntax. We can use that expression in our program instead of using printf:

#include <unistd.h>
int main(void) {
  write(1, "hello, world!\n", 14);
  return 0;
}

But write(...) here is a C function call, not a system call! write is a wrapper around the system call, and its implementation varies depending on the OS. The program above works on Linux and on macOS for this reason.

But what is the function write doing? Going one level deeper, we can call the syscall function with the same arguments, plus the argument SYS_write specifying the system call number:

#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
  syscall(SYS_write, 1, "hello, world!\n", 14);
  return 0;
}

What is SYS_write? We can print it out:

#include <stdio.h>
#include <sys/syscall.h>
int main(void) {
  printf("%d\n", SYS_write);
  return 0;
}

On Linux x86-64, this prints 1. On macOS, it prints 4. We’re now in the realms of OS-dependence and architecture-dependence. Now what is is syscall(...) doing? It’s defined in assembly!

.text
ENTRY (syscall)
movq %rdi, %rax		/* Syscall number -> rax.  */
movq %rsi, %rdi		/* shift arg1 - arg5.  */
movq %rdx, %rsi
movq %rcx, %rdx
movq %r8, %r10
movq %r9, %r8
movq 8(%rsp),%r9	/* arg6 is on the stack.  */
syscall			/* Do the system call.  */
cmpq $-4095, %rax	/* Check %rax for error.  */
jae SYSCALL_ERROR_LABEL	/* Jump to error handler if error.  */
ret			/* Return to caller.  */

PSEUDO_END (syscall)

syscall(...) puts its arguments in the right registers for the system call, then performs the system call with the syscall assembly instruction. We can do this ourselves in C using some magic GCC inline assembly!

int main(void) {
  register int    syscall_no  asm("rax") = 1;
  register int    arg1        asm("rdi") = 1;
  register char*  arg2        asm("rsi") = "hello, world!\n";
  register int    arg3        asm("rdx") = 14;
  asm("syscall");
  return 0;
}

What can computers do? What are the limits of mathematics? And just how busy can a busy beaver be? This year, I’m writing Busy Beavers, a unique interactive book on computability theory. You and I will take a practical and modern approach to answering these questions — or at least learning why some questions are unanswerable!

It’s only $19, and you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

After months of secret toil, I and Andrew Carr released Everyday Data Science, a unique interactive online course! You’ll make the perfect glass of lemonade using Thompson sampling. You’ll lose weight with differential equations. And you might just qualify for the Olympics with a bit of statistics!

It’s $29, but you can get 50% off if you find the discount code ... Not quite. Hackers use the console!

More by Jim

Tagged #programming, #c. All content copyright James Fisher 2018. This post is not associated with my employer. Found an error? Edit this page.