Calling System Calls

There are two ways system calls are being called in the user space. Both of them will eventually call the syscall instruction but glibc provides a wrapper around that instruction using a function call.

  • glibc library call - this moves the arguments to the right registers before calling the syscall instruction.
  • syscall assembly instruction - to actually hand over the work to the kernel.

Glibc syscall() interface

  • There is a library function in glibc named as syscall, you can read about it in the man pages by the command man 2 syscall.
  • We already have the code of glibc with us.
  • See the function in the file glibc-2.23/sysdeps/unix/sysv/linux/x86_64/syscall.S
  • On reading the code you will see that the function is moving the argument values to the registers and then calling the assembly instruction syscall.
  • As syscall here is a user space glibc library function, first the arguments will be in the registers used for calling user space functions. Once this is done, as the system call is being called, the arguments will be used into the registers where the kernel wishes to find the arguments. See Reiterating The Above Again
  • Code for syscall(2) library function. File is glibc-2.24/sysdeps/unix/sysv/linux/x86_64/syscall.S

Note

Remember the note above. As syscall is a function which we called in user space, the registers are different. We now need to pick and place the registers in a way that the system call understands it. This is shown in the code below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* Copyright (C) 2001-2016 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */

#include <sysdep.h>

/* Please consult the file sysdeps/unix/sysv/linux/x86-64/sysdep.h for
   more information about the value -4095 used below.  */

/* Usage: long syscall (syscall_number, arg1, arg2, arg3, arg4, arg5, arg6)
   We need to do some arg shifting, the syscall_number will be in
   rax.  */


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

We know now that for calling a system call we just need to set the right arguments in the register and then call the syscall instruction.

Register %rax needs the system call number. So where are the system call numbers defined? Here we can see the glibc code to see the mapping of the number and the system call. Or you can see this in a header file in the system’s include directory.

Let us see a excerpt from the file /usr/include/x86_64-linux-gnu/asm/unistd_64.h

#define __NR_read   0
#define __NR_write  1
#define __NR_open   2
#define __NR_close  3
#define __NR_stat   4

Here you can see that the system calls have numbers associated with them.

Difference between syscall() glibc interface and syscall assembly instruction

In this section we will write some data to the STDOUT (terminal) using three methods.

  • First we will issue a write() system call.
  • Second we will use the syscall() function in glibc.
  • Third we will write assembly code and call the syscall instruction.

This will help us understand system calls in more detail.

Now armed with the knowledge of how to call system calls let us write some assembly code where we call a system call.

write() system call

We will start by exploring the write system call a bit. In the following code we will write hello world on the screen. We will not use printf for this, rather we will use 1 (the standard descriptor for writing to the terminal) and write system call for it.

We need to do this so that we understand our assembly level program a bit better.

code_system_calls/07/write.c
1
2
3
4
5
6
7
8
#include <fcntl.h>
#include <unistd.h>

int main ()
{
    write (1, "Hello World", 11);
    return 0;
}

You should go through the assembly code of the C file. Use command gcc -S filename.c This will generate the assembly file with .s extension. If you go through the assembly code you will see a call to write function. This function is defined in the glibc.

syscall() function

Now we will do the same using the syscall interface which the glibc provides.

1
2
3
4
5
6
7
8
9
#include <unistd.h>
#include <sys/syscall.h>


int main ()
{
    syscall (1, 1, "Hello World", 11);
    return 0;
}

Here is the assembly code for the above file. This is generated by using the gcc -S filename.c command. This generates a file with name as filename.s

You can see how the arguments are been copied to the registers for calling the function syscall(). This is being done so that in the syscall() function the arguments can be moved to the right registers for calling the syscall instruction.

syscall instruction

Now we will do the same in our assembly code. The idea here is to move the right values to the right registers and then just call the syscall instruction. The same is achieved is by calling the syscall() function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
section .text
    global _start
    _start:                 ; ELF entry point
     ; 1 is the number for syscall write ().

    mov rax, 1              
    ; 1 is the STDOUT file descriptor.

    mov rdi, 1              

    ; buffer to be printed.

    mov rsi, message        

    ; length of buffer

    mov rdx, [messageLen]       

    ; call the syscall instruction
    syscall
    
    ; sys_exit
    mov rax, 60

    ; return value is 0
    mov rdi, 0

    ; call the assembly instruction
    syscall
    
section .data
    messageLen: dq message.end-message
    message: db 'Hello World', 10
.end:

Makefile for assembling the code.

1
2
3
4
5
6
7
all:
	nasm -felf64 write.asm 	# Assemble the program.
	ld write.o -o elf.write	

clean:
	rm -rf *.o
	

Run the make command and run the file elf.write. You will see the output of your program on the screen.

$ make
nasm -felf64 write.asm  # Assemble the program.
ld write.o -o elf.write

$ ./elf.write
Hello World

Conclusion

In this chapter we saw the different ways of calling a system call. The three ways are

  • to call the function directly like calling write directly.
  • to call the glibc interface for calling system calls namely syscall()
  • to directly call the syscall instruction from any assembly file.