Date created: 04/13/19 11:16:29. Last modified: 06/03/19 08:04:55

Syscalls On Linux

32-bit Legacy Syscalls

The INT instruction is executed which triggers a software interrupt. The INT instruction is called with argument 0x80 which is the vector to signal a software interrupt to the Kernel and tell the Kernel to execute a syscall. On Intel chips interrupt numbers 0 – 31 are reserved for Intel’s usages. Above that, the Kernel allocates the software and hardware interrupts as required.

From: https://github.com/torvalds/linux/blob/v3.13/arch/x86/include/asm/irq_vectors.h#L17

* Vectors 0 ... 31 : system traps and exceptions - hardcoded events
* Vectors 32 ... 127 : device interrupts
* Vector 128 : legacy int80 syscall interface
* Vectors 129 ... INVALIDATE_TLB_VECTOR_START-1 except 204 : device interrupts
* Vectors INVALIDATE_TLB_VECTOR_START ... 255 : special interrupts

The legacy interrupt vector 128 (0x80) is defined further down in the same file. From: https://github.com/torvalds/linux/blob/v3.13/arch/x86/include/asm/irq_vectors.h#L49

#define IA32_SYSCALL_VECTOR 0x80
#ifdef CONFIG_X86_32
# define SYSCALL_VECTOR 0x80
#endif

Above, 0x80 is defined as the IA32_SYSCALL interrupt vector, a.k.a gate number. In the below code, that interrupt gate number is mapped to the ia32_syscall ISR (Interrupt Service Routine). From: https://github.com/torvalds/linux/blob/v3.13/arch/x86/kernel/traps.c#L770

#ifdef CONFIG_IA32_EMULATION
set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#endif

Finally the ia32_syscall routine is define here: https://github.com/torvalds/linux/blob/v3.13/arch/x86/ia32/ia32entry.S#L378

 

All of the above shows the path of executing the INT instruction with 0x80 as the argument, how it will trigger a software interrupt call to the Kernel to run a syscall. Before the INT instruction is called though, the required syscall number being requested must be stored in EAX and any optional parameters for the syscall must be stored in EBX, ECX, EDX, ESI and EDI. This is recorded in the comments of the code section linked above where ia32_syscall is defined:

/* 
* Emulated IA32 system calls via int 0x80.
*
* Arguments:
* %eax System call number.
* %ebx Arg1
* %ecx Arg2
* %edx Arg3
* %esi Arg4
* %edi Arg5
* %ebp Arg6 [note: not saved in the stack frame, should not be touched]

The syscall numbers to use via this legacy method of calling syscalls via a software interrupt are stored here: https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl

This website shows the arguments required for each syscall: http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

 

x64 Syscalls

The SYSENTER and SYSEXIT instructions are the "modern" way to use syscalls in Intel assembly. The SYSCALL instruction was introduce by AMD. Intel also supports a SYSCALL instruction which is an x64 instruction, SYSENTER/SYSEXIT are IA32 instructions.

The SYSCALL instruction allows an assembly program to directly trigger a soft interrupt and specify the required syscall number in a single instruction. The requested Kernel syscall number is placed into EAX as with the legacy process using the INT instruction but, no argument needs to be passed to the SYSCALL instruction itself.

The x64 syscalls define 60 as the exist syscall here: https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl#L71

The legacy x86 syscalls define syscall 1 as the exist syscall here:
https://github.com/torvalds/linux/blob/v3.13/arch/x86/syscalls/syscall_32.tbl

Note the above is a link to Kernel version 3.13 code – this table was later removed.

These two programs produce the exact same output;

$ cat exit_int.asm
section .data

EXIT_SUCCESS equ 0 ; return/exit code
SYS_exit equ 1     ; exit syscall from /usr/include/x86_64-linux-gnu/asm/unistd_32.h

section .text
global _start
_start:

mov rax, SYS_exit
mov rdi, EXIT_SUCCESS
int 0x80
$ cat exit_syscall.asm
section .data

EXIT_SUCCESS equ 0 ; return/exit code
SYS_exit equ 60    ; exit syscall from

section .text
global _start
_start:

mov rax, SYS_exit
mov rdi, EXIT_SUCCESS
syscall
$ yasm -Worphan-labels -g dwarf2 -f elf64 exit_int.asm -l exit_int.lst
$ yasm -Worphan-labels -g dwarf2 -f elf64 exit_syscall.asm -l exit_syscall.lst

Below it can be seen that the only difference between the two applications is the syscall number which has been pushed to EAX (0x1 in the INT instruction example and 0x3C in the SYSCALL instruction example) and the opcode for the software interrupt instruction (0xCD is the Intel opcode for the INT instruction and 0x0F05 is the Intel opcode for the SYSCALL instruction):

$ cat exit_int.lst 
1 %line 1+1 exit_int.asm
2 [section .data]
3
4 EXIT_SUCCESS equ 0
5 SYS_exit equ 1
6
7 [section .text]
8 [global _start]
9 _start:
10
11 00000000 48C7C001000000 mov rax, SYS_exit
12 00000007 48C7C700000000 mov rdi, EXIT_SUCCESS
13 0000000E CD80 int 0x80
$ cat exit_syscall.lst 
1 %line 1+1 exit_syscall.asm
2 [section .data]
3
4 EXIT_SUCCESS equ 0
5 SYS_exit equ 60
6
7 [section .text]
8 [global _start]
9 _start:
10
11 00000000 48C7C03C000000 mov rax, SYS_exit
12 00000007 48C7C700000000 mov rdi, EXIT_SUCCESS
13 0000000E 0F05 syscall

 


Previous page: RET vs. SYSCALL vs. C exit()
Next page: Bash Notes