Date created: Sunday, April 21, 2019 11:19:15 AM. Last modified: Monday, June 3, 2019 8:04:58 AM

RET vs. SYSCALL vs. C exit()

Return

The need for return()/RET is when ending a thread or process and returning the status to the parent thread or OS/Kernel. A function call may also use return()/RET if it returns a result. Using return() in C translates to the RET assembly instruction which can return one or more values (at least one, the status code, is required, in both languages).

Compile and run the C code:


$ cat c_return.c

int main () {

return 0;

}

$ gcc -g -O0 -o c_return c_return.c

$ file c_return
c_return: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7cb093ecac606c7792f1a3f5a9bc757afbb801b7, with debug_info, not stripped

$ ./c_return
$ echo $?
0

Compile the C code to assembly:


# Produces object file c_return.o
$ gcc -g -c c_return.c

$ file c_return.o
c_return.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped

$ objdump -d -M intel -S c_return.o

c_return.o: file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
int main () {
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp

return 0;
4: b8 00 00 00 00 mov eax,0x0

}
9: 5d pop rbp
a: c3 ret

Compile the C to assembly then compile and execute the assembly:


# Generates assembly code from C code as c_return.s
$ gcc -g -S c_return.c
# Compile the assembly into an object file c_return.o
$ gcc -g -c c_return.s
$ objdump -d -M intel -S c_return.o

c_return.o: file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: b8 00 00 00 00 mov eax,0x0
9: 5d pop rbp
a: c3 ret

# Link and compile the object file into an executable c_return
$ gcc -g c_return.o -o c_return

$ file c_return
c_return: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=0cdede0af9ffd055c1d344f435b7fd392fa06a2b, with debug_info, not stripped

$ ./c_return
$ echo $?
0

 

SYSCALL

The SYSCALL instruction can be used to terminate a program that is either return a value by placing it somewhere in memory (like on the stack) and not via the RET instruction, or when the program is not written in C (i.e. a raw assembly program) that doesn't use the standard C libraries with functions like exit().


$ cat exit_syscall.s

.intel_syntax noprefix
.section .data

.section .text
.globl _start
_start:

mov rax, 60 # Linux exit syscall
mov rdi, 0 # Return code, 0 == exit successfully
syscall

# Compile assembly code into an object (.o) file of machine code:
$ as exit_syscall.s -o exit_syscall.o
$ file exit_syscall.o
exit_syscall.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

# Optionally dump the opcodes in the compiled assembly:
$ objdump -d -M intel -S exit_syscall.o

exit_syscall.o: file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <_start>:
0: 48 c7 c0 3c 00 00 00 mov rax,0x3c
7: 48 c7 c7 00 00 00 00 mov rdi,0x0
e: 0f 05 syscall

# Link the assembly into an executable binary:
$ ld -o exit_syscall exit_syscall.o
$ file exit_syscall
exit_syscall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ ./exit_syscall
$ echo $?
0

 

Exit

The C function exit() is calling into the standard library glibc on Linux which works with the Kernel to call various clean-up function as the process (or thread) is terminated. Using exit() in C compiles in assembly to a link to the library function for exit().

Compile and run the C code:


$ cat c_exit.c

#include <stdlib.h>

int main () {

exit(0);

}

$ gcc -g -O0 -o c_exit c_exit.c
$ file c_exit
c_exit: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5ceaaca6c7f7d95d79fff01af3b2dc973ea45535, with debug_info, not stripped
$ ./c_exit
$ echo $?
0

Compile the C code to assembly:


# Produces object file c_exit.o
$ gcc -g -c c_exit.c

$ file c_exit.o
c_exit.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped

$ objdump -d -M intel -S c_exit.o

c_exit.o: file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
#include <stdlib.h>

int main () {
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp

exit(0);
4: bf 00 00 00 00 mov edi,0x0
9: e8 00 00 00 00 call e <main+0xe>

Compile the C to assembly then compile and execute the assembly:


# Generates assembly code from C code as c_exit.s
$ gcc -g -S c_exit.c
# Compile the assembly into an object file c_exit.o
$ gcc -g -c c_exit.s
$ objdump -d -M intel -S c_exit.o

c_exit.o: file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: bf 00 00 00 00 mov edi,0x0
9: e8 00 00 00 00 call e <main+0xe>

# Link and compile the object file into an executable c_exit
$ gcc -g c_exit.o -o c_exit
$ file c_exit
c_exit: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a1f86b81ead50f59e7b1d925dfdb979a1470204e, with debug_info, not stripped
$ ./c_exit
$ echo $?
0