3. *BSD system calls

In the *BSD family, direct system calls (i.e. through the 0x80 interrupt) are slightly different than in Linux, while there's no difference in indirect system calls (i.e. using the libc functions addresses).

The numbers of the syscalls are listed in the /usr/src/sys/kern/syscalls.master file, which also contains the prototypes of the syscall functions. Here are the first lines of the file on OpenBSD:

1       STD             { void sys_exit(int rval); }
2       STD             { int sys_fork(void); }
3       STD             { ssize_t sys_read(int fd, void *buf, size_t nbyte); }
4       STD             { ssize_t sys_write(int fd, const void *buf, \
                            size_t nbyte); }
5       STD             { int sys_open(const char *path, \
                            int flags, ... mode_t mode); }
6       STD             { int sys_close(int fd); }
7       STD             { pid_t sys_wait4(pid_t pid, int *status, int options, \
                            struct rusage *rusage); }
8       COMPAT_43       { int sys_creat(const char *path, mode_t mode); } ocreat

The first column contains the system call number, the second contains the type of the system call and the third the prototype of the function.

Unlike Linux, *BSD system calls don't use the fastcall convention (i.e. passing arguments in registers), but use the C calling convention instead, pushing arguments on the stack. Arguments are pushed in reverse order (from right to left), so that they are extracted in the correct order by the function. Immediately after the system call returns, the stack needs to be cleaned up by adding to the stack pointer (ESP) a number equal to the size, in bytes, of the arguments (to put it simply, you have to add the number of arguments multiplied by 4).

The role of the EAX register, instead, remains the same: it must contain the syscall number and will eventually contain the return value. Therefore, to recap, executing a system call requires four steps:

  1. storing the syscall number in EAX;
  2. pushing (in reverse order) the arguments on the stack;
  3. executing the 0x80 software interrupt;
  4. cleaning up the stack.

The previous example for Linux, now becomes on *BSD:

mov  eax, 1    ; Syscall number
push dword 0   ; rval
push eax       ; Push one more dword (see below)
int  0x80      ; 0x80 interrupt
add  esp, 8    ; Clean up the stack

As you can see, before executing the software interrupt, you need to push one extra dword on the stack (any dword will do); for an in-depth discussion on this topic, please refer to [FreeBSD].